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