Completed
Pull Request — master (#61)
by
unknown
02:49
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 : ''),
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',
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',
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 isn\'t an image.');
200
            }
201
202
            $type = strtoupper(str_replace('image/', '', $mimetype));
203
204
            $property .= ";ENCODING=b;TYPE=" . $type;
205
        } else {
206
207
            $propertySuffix = ';VALUE=URL';
208
209
            $headers = get_headers($url);
210
            $imageTypeMatched = preg_match('/Content-Type:\simage\/([a-z]+)/i', $headers[8], $m);
211
            if(!$imageTypeMatched) {
212
                throw new VCardMediaException('Returned data isn\'t an image.');
213
            }
214
215
            $propertySuffix .= ';TYPE=' . strtoupper($m[1]);
216
217
            $property = $property . $propertySuffix;
218
            $value = $url;
219
220
        }
221
222
        $this->setProperty(
223
            $element,
224
            $property,
225
            $value
226
        );
227
    }
228
229
    /**
230
     * Add name
231
     *
232
     * @param  string [optional] $lastName
233
     * @param  string [optional] $firstName
234
     * @param  string [optional] $additional
235
     * @param  string [optional] $prefix
236
     * @param  string [optional] $suffix
237
     * @return $this
238
     */
239
    public function addName(
240
        $lastName = '',
241
        $firstName = '',
242
        $additional = '',
243
        $prefix = '',
244
        $suffix = ''
245
    ) {
246
        // define values with non-empty values
247
        $values = array_filter(array(
248
            $prefix,
249
            $firstName,
250
            $additional,
251
            $lastName,
252
            $suffix,
253
        ));
254
255
        // define filename
256
        $this->setFilename($values);
257
258
        // set property
259
        $property = $lastName . ';' . $firstName . ';' . $additional . ';' . $prefix . ';' . $suffix;
260
        $this->setProperty(
261
            'name',
262
            'N',
263
            $property
264
        );
265
266
        // is property FN set?
267
        if (!$this->hasProperty('FN')) {
268
            // set property
269
            $this->setProperty(
270
                'fullname',
271
                'FN',
272
                trim(implode(' ', $values))
273
            );
274
        }
275
276
        return $this;
277
    }
278
279
    /**
280
     * Add note
281
     *
282
     * @param  string $note
283
     * @return $this
284
     */
285
    public function addNote($note)
286
    {
287
        $this->setProperty(
288
            'note',
289
            'NOTE',
290
            $note
291
        );
292
293
        return $this;
294
    }
295
296
    /**
297
     * Add phone number
298
     *
299
     * @param  string            $number
300
     * @param  string [optional] $type
301
     *                                   Type may be PREF | WORK | HOME | VOICE | FAX | MSG |
302
     *                                   CELL | PAGER | BBS | CAR | MODEM | ISDN | VIDEO
303
     *                                   or any senseful combination, e.g. "PREF;WORK;VOICE"
304
     * @return $this
305
     */
306
    public function addPhoneNumber($number, $type = '')
307
    {
308
        $this->setProperty(
309
            'phoneNumber',
310
            'TEL' . (($type != '') ? ';' . $type : ''),
311
            $number
312
        );
313
314
        return $this;
315
    }
316
317
    /**
318
     * Add Photo
319
     *
320
     * @param  string $url     image url or filename
321
     * @param  bool   $include Include the image in our vcard?
322
     * @return $this
323
     */
324
    public function addPhoto($url, $include = true)
325
    {
326
        $this->addMedia(
327
            'PHOTO',
328
            $url,
329
            $include,
330
            'photo'
331
        );
332
333
        return $this;
334
    }
335
336
    /**
337
     * Add URL
338
     *
339
     * @param  string            $url
340
     * @param  string [optional] $type Type may be WORK | HOME
341
     * @return $this
342
     */
343
    public function addURL($url, $type = '')
344
    {
345
        $this->setProperty(
346
            'url',
347
            'URL' . (($type != '') ? ';' . $type : ''),
348
            $url
349
        );
350
351
        return $this;
352
    }
353
354
    /**
355
     * Build VCard (.vcf)
356
     *
357
     * @return string
358
     */
359
    public function buildVCard()
360
    {
361
        // init string
362
        $string = "BEGIN:VCARD\r\n";
363
        $string .= "VERSION:3.0\r\n";
364
        $string .= "REV:" . date("Y-m-d") . "T" . date("H:i:s") . "Z\r\n";
365
366
        // loop all properties
367
        $properties = $this->getProperties();
368
        foreach ($properties as $property) {
369
            // add to string
370
            $string .= $this->fold($property['key'] . ':' . $property['value'] . "\r\n");
371
        }
372
373
        // add to string
374
        $string .= "END:VCARD\r\n";
375
376
        // return
377
        return $string;
378
    }
379
380
    /**
381
     * Build VCalender (.ics) - Safari (< iOS 8) can not open .vcf files, so we have build a workaround.
382
     *
383
     * @return string
384
     */
385
    public function buildVCalendar()
386
    {
387
        // init dates
388
        $dtstart = date("Ymd") . "T" . date("Hi") . "00";
389
        $dtend = date("Ymd") . "T" . date("Hi") . "01";
390
391
        // init string
392
        $string = "BEGIN:VCALENDAR\n";
393
        $string .= "VERSION:2.0\n";
394
        $string .= "BEGIN:VEVENT\n";
395
        $string .= "DTSTART;TZID=Europe/London:" . $dtstart . "\n";
396
        $string .= "DTEND;TZID=Europe/London:" . $dtend . "\n";
397
        $string .= "SUMMARY:Click attached contact below to save to your contacts\n";
398
        $string .= "DTSTAMP:" . $dtstart . "Z\n";
399
        $string .= "ATTACH;VALUE=BINARY;ENCODING=BASE64;FMTTYPE=text/directory;\n";
400
        $string .= " X-APPLE-FILENAME=" . $this->getFilename() . "." . $this->getFileExtension() . ":\n";
401
402
        // base64 encode it so that it can be used as an attachemnt to the "dummy" calendar appointment
403
        $b64vcard = base64_encode($this->buildVCard());
404
405
        // chunk the single long line of b64 text in accordance with RFC2045
406
        // (and the exact line length determined from the original .ics file exported from Apple calendar
407
        $b64mline = chunk_split($b64vcard, 74, "\n");
408
409
        // need to indent all the lines by 1 space for the iphone (yes really?!!)
410
        $b64final = preg_replace('/(.+)/', ' $1', $b64mline);
411
        $string .= $b64final;
412
413
        // output the correctly formatted encoded text
414
        $string .= "END:VEVENT\n";
415
        $string .= "END:VCALENDAR\n";
416
417
        // return
418
        return $string;
419
    }
420
421
    /**
422
     * Returns the browser user agent string.
423
     *
424
     * @return string
425
     */
426
    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...
427
    {
428
        if (array_key_exists('HTTP_USER_AGENT', $_SERVER)) {
429
            $browser = strtolower($_SERVER['HTTP_USER_AGENT']);
430
        } else {
431
            $browser = 'unknown';
432
        }
433
434
        return $browser;
435
    }
436
437
    /**
438
     * Decode
439
     *
440
     * @param  string $value The value to decode
441
     * @return string decoded
442
     */
443
    private function decode($value)
444
    {
445
        // convert cyrlic, greek or other caracters to ASCII characters
446
        return Transliterator::transliterate($value);
447
    }
448
449
    /**
450
     * Download a vcard or vcal file to the browser.
451
     */
452
    public function download()
453
    {
454
        // define output
455
        $output = $this->getOutput();
456
457
        foreach ($this->getHeaders(false) as $header) {
458
            header($header);
459
        }
460
461
        // echo the output and it will be a download
462
        echo $output;
463
    }
464
465
    /**
466
     * Fold a line according to RFC2425 section 5.8.1.
467
     *
468
     * @link http://tools.ietf.org/html/rfc2425#section-5.8.1
469
     * @param  string $text
470
     * @return mixed
471
     */
472
    protected function fold($text)
473
    {
474
        if (strlen($text) <= 75) {
475
            return $text;
476
        }
477
478
        // split, wrap and trim trailing separator
479
        return substr(chunk_split($text, 73, "\r\n "), 0, -3);
480
    }
481
482
    /**
483
     * Get output as string
484
     * @deprecated in the future
485
     *
486
     * @return string
487
     */
488
    public function get()
489
    {
490
        return $this->getOutput();
491
    }
492
493
    /**
494
     * Get charset
495
     *
496
     * @return string
497
     */
498
    public function getCharset()
499
    {
500
        return $this->charset;
501
    }
502
503
    /**
504
     * Get content type
505
     *
506
     * @return string
507
     */
508
    public function getContentType()
509
    {
510
        return ($this->isIOS7()) ?
511
            'text/x-vcalendar' : 'text/x-vcard';
512
    }
513
514
    /**
515
     * Get filename
516
     *
517
     * @return string
518
     */
519
    public function getFilename()
520
    {
521
        return $this->filename;
522
    }
523
524
    /**
525
     * Get file extension
526
     *
527
     * @return string
528
     */
529
    public function getFileExtension()
530
    {
531
        return ($this->isIOS7()) ?
532
            'ics' : 'vcf';
533
    }
534
535
    /**
536
     * Get headers
537
     *
538
     * @param  bool  $asAssociative
539
     * @return array
540
     */
541
    public function getHeaders($asAssociative)
542
    {
543
        $contentType        = $this->getContentType() . '; charset=' . $this->getCharset();
544
        $contentDisposition = 'attachment; filename=' . $this->getFilename() . '.' . $this->getFileExtension();
545
        $contentLength      = strlen($this->getOutput());
546
        $connection         = 'close';
547
548
        if ((bool) $asAssociative) {
549
            return array(
550
                'Content-type'        => $contentType,
551
                'Content-Disposition' => $contentDisposition,
552
                'Content-Length'      => $contentLength,
553
                'Connection'          => $connection,
554
            );
555
        }
556
557
        return array(
558
            'Content-type: ' . $contentType,
559
            'Content-Disposition: ' . $contentDisposition,
560
            'Content-Length: ' . $contentLength,
561
            'Connection: ' . $connection,
562
        );
563
    }
564
565
    /**
566
     * Get output as string
567
     * iOS devices (and safari < iOS 8 in particular) can not read .vcf (= vcard) files.
568
     * So I build a workaround to build a .ics (= vcalender) file.
569
     *
570
     * @return string
571
     */
572
    public function getOutput()
573
    {
574
        $output = ($this->isIOS7()) ?
575
            $this->buildVCalendar() : $this->buildVCard();
576
577
        // we need to decode the output for outlook
578
        if ($this->getCharset() == 'utf-8') {
579
            $output = utf8_decode($output);
580
        }
581
582
        return $output;
583
    }
584
585
    /**
586
     * Get properties
587
     *
588
     * @return array
589
     */
590
    public function getProperties()
591
    {
592
        return $this->properties;
593
    }
594
595
    /**
596
     * Has property
597
     *
598
     * @param  string $key
599
     * @return bool
600
     */
601
    public function hasProperty($key)
602
    {
603
        $properties = $this->getProperties();
604
605
        foreach ($properties as $property) {
606
            if ($property['key'] === $key && $property['value'] !== '') {
607
                return true;
608
            }
609
        }
610
611
        return false;
612
    }
613
614
    /**
615
     * Is iOS - Check if the user is using an iOS-device
616
     *
617
     * @return bool
618
     */
619
    public function isIOS()
620
    {
621
        // get user agent
622
        $browser = $this->getUserAgent();
623
624
        return (strpos($browser, 'iphone') || strpos($browser, 'ipod') || strpos($browser, 'ipad'));
625
    }
626
627
    /**
628
     * Is iOS less than 7 (should cal wrapper be returned)
629
     *
630
     * @return bool
631
     */
632
    public function isIOS7()
633
    {
634
        return ($this->isIOS() && $this->shouldAttachmentBeCal());
635
    }
636
637
    /**
638
     * Save to a file
639
     *
640
     * @return void
641
     */
642
    public function save()
643
    {
644
        $file = $this->getFilename() . '.' . $this->getFileExtension();
645
646
        file_put_contents(
647
            $file,
648
            $this->getOutput()
649
        );
650
    }
651
652
    /**
653
     * Set charset
654
     *
655
     * @param  mixed  $charset
656
     * @return void
657
     */
658
    public function setCharset($charset)
659
    {
660
        $this->charset = $charset;
661
    }
662
663
    /**
664
     * Set filename
665
     *
666
     * @param  mixed  $value
667
     * @param  bool   $overwrite [optional] Default overwrite is true
668
     * @param  string $separator [optional] Default separator is an underscore '_'
669
     * @return void
670
     */
671
    public function setFilename($value, $overwrite = true, $separator = '_')
672
    {
673
        // recast to string if $value is array
674
        if (is_array($value)) {
675
            $value = implode($separator, $value);
676
        }
677
678
        // trim unneeded values
679
        $value = trim($value, $separator);
680
681
        // remove all spaces
682
        $value = preg_replace('/\s+/', $separator, $value);
683
684
        // if value is empty, stop here
685
        if (empty($value)) {
686
            return;
687
        }
688
689
        // decode value + lowercase the string
690
        $value = strtolower($this->decode($value));
691
692
        // urlize this part
693
        $value = Transliterator::urlize($value);
694
695
        // overwrite filename or add to filename using a prefix in between
696
        $this->filename = ($overwrite) ?
697
            $value : $this->filename . $separator . $value;
698
    }
699
700
    /**
701
     * Set property
702
     *
703
     * @param  string $element The element name you want to set, f.e.: name, email, phoneNumber, ...
704
     * @param  string $key
705
     * @param  string $value
706
     * @return void
707
     */
708
    private function setProperty($element, $key, $value)
709
    {
710
        if (!in_array($element, $this->multiplePropertiesForElementAllowed)
711
            && isset($this->definedElements[$element])
712
        ) {
713
            throw new Exception('You can only set "' . $element . '" once.');
714
        }
715
716
        // we define that we set this element
717
        $this->definedElements[$element] = true;
718
719
        // adding property
720
        $this->properties[] = array(
721
            'key' => $key,
722
            'value' => $value
723
        );
724
    }
725
726
    /**
727
     * Checks if we should return vcard in cal wrapper
728
     *
729
     * @return bool
730
     */
731
    protected function shouldAttachmentBeCal()
732
    {
733
        $browser = $this->getUserAgent();
734
735
        $matches = array();
736
        preg_match('/os (\d+)_(\d+)\s+/', $browser, $matches);
737
        $version = isset($matches[1]) ? ((int) $matches[1]) : 999;
738
739
        return ($version < 8);
740
    }
741
}
742