1 | <?php |
||||
2 | |||||
3 | namespace JeroenDesloovere\VCard; |
||||
4 | |||||
5 | /* |
||||
6 | * This file is part of the VCard PHP Class from Jeroen Desloovere. |
||||
7 | * |
||||
8 | * For the full copyright and license information, please view the license |
||||
9 | * file that was distributed with this source code. |
||||
10 | */ |
||||
11 | |||||
12 | use Behat\Transliterator\Transliterator; |
||||
13 | |||||
14 | /** |
||||
15 | * VCard PHP Class to generate .vcard files and save them to a file or output as a download. |
||||
16 | */ |
||||
17 | class VCard |
||||
18 | { |
||||
19 | /** |
||||
20 | * definedElements |
||||
21 | * |
||||
22 | * @var array |
||||
23 | */ |
||||
24 | private $definedElements; |
||||
25 | |||||
26 | /** |
||||
27 | * Filename |
||||
28 | * |
||||
29 | * @var string |
||||
30 | */ |
||||
31 | private $filename; |
||||
32 | |||||
33 | /** |
||||
34 | * Save Path |
||||
35 | * |
||||
36 | * @var string |
||||
37 | */ |
||||
38 | private $savePath = null; |
||||
39 | |||||
40 | /** |
||||
41 | * Multiple properties for element allowed |
||||
42 | * |
||||
43 | * @var array |
||||
44 | */ |
||||
45 | private $multiplePropertiesForElementAllowed = [ |
||||
46 | 'email', |
||||
47 | 'address', |
||||
48 | 'phoneNumber', |
||||
49 | 'url', |
||||
50 | 'label' |
||||
51 | ]; |
||||
52 | |||||
53 | /** |
||||
54 | * Properties |
||||
55 | * |
||||
56 | * @var array |
||||
57 | */ |
||||
58 | private $properties; |
||||
59 | |||||
60 | /** |
||||
61 | * Default Charset |
||||
62 | * |
||||
63 | * @var string |
||||
64 | */ |
||||
65 | public $charset = 'utf-8'; |
||||
66 | |||||
67 | /** |
||||
68 | * Add address |
||||
69 | * |
||||
70 | * @param string [optional] $name |
||||
71 | * @param string [optional] $extended |
||||
72 | * @param string [optional] $street |
||||
73 | * @param string [optional] $city |
||||
74 | * @param string [optional] $region |
||||
75 | * @param string [optional] $zip |
||||
76 | * @param string [optional] $country |
||||
77 | * @param string [optional] $type |
||||
78 | * $type may be DOM | INTL | POSTAL | PARCEL | HOME | WORK |
||||
79 | * or any combination of these: e.g. "WORK;PARCEL;POSTAL" |
||||
80 | * @return $this |
||||
81 | */ |
||||
82 | public function addAddress( |
||||
83 | $name = '', |
||||
84 | $extended = '', |
||||
85 | $street = '', |
||||
86 | $city = '', |
||||
87 | $region = '', |
||||
88 | $zip = '', |
||||
89 | $country = '', |
||||
90 | $type = 'WORK;POSTAL' |
||||
91 | ) { |
||||
92 | // init value |
||||
93 | $value = $name . ';' . $extended . ';' . $street . ';' . $city . ';' . $region . ';' . $zip . ';' . $country; |
||||
94 | |||||
95 | // set property |
||||
96 | $this->setProperty( |
||||
97 | 'address', |
||||
98 | 'ADR' . (($type != '') ? ';' . $type : '') . $this->getCharsetString(), |
||||
99 | $value |
||||
100 | ); |
||||
101 | |||||
102 | return $this; |
||||
103 | } |
||||
104 | |||||
105 | /** |
||||
106 | * Add birthday |
||||
107 | * |
||||
108 | * @param string $date Format is YYYY-MM-DD |
||||
109 | * @return $this |
||||
110 | */ |
||||
111 | public function addBirthday($date) |
||||
112 | { |
||||
113 | $this->setProperty( |
||||
114 | 'birthday', |
||||
115 | 'BDAY', |
||||
116 | $date |
||||
117 | ); |
||||
118 | |||||
119 | return $this; |
||||
120 | } |
||||
121 | |||||
122 | /** |
||||
123 | * Add company |
||||
124 | * |
||||
125 | * @param string $company |
||||
126 | * @param string $department |
||||
127 | * @return $this |
||||
128 | */ |
||||
129 | public function addCompany($company, $department = '') |
||||
130 | { |
||||
131 | $this->setProperty( |
||||
132 | 'company', |
||||
133 | 'ORG' . $this->getCharsetString(), |
||||
134 | $company |
||||
135 | . ($department != '' ? ';' . $department : '') |
||||
136 | ); |
||||
137 | |||||
138 | // if filename is empty, add to filename |
||||
139 | if ($this->filename === null) { |
||||
140 | $this->setFilename($company); |
||||
141 | } |
||||
142 | |||||
143 | return $this; |
||||
144 | } |
||||
145 | |||||
146 | /** |
||||
147 | * Add email |
||||
148 | * |
||||
149 | * @param string $address The e-mail address |
||||
150 | * @param string [optional] $type The type of the email address |
||||
151 | * $type may be PREF | WORK | HOME |
||||
152 | * or any combination of these: e.g. "PREF;WORK" |
||||
153 | * @return $this |
||||
154 | */ |
||||
155 | public function addEmail($address, $type = '') |
||||
156 | { |
||||
157 | $this->setProperty( |
||||
158 | 'email', |
||||
159 | 'EMAIL;INTERNET' . (($type != '') ? ';' . $type : ''), |
||||
160 | $address |
||||
161 | ); |
||||
162 | |||||
163 | return $this; |
||||
164 | } |
||||
165 | |||||
166 | /** |
||||
167 | * Add jobtitle |
||||
168 | * |
||||
169 | * @param string $jobtitle The jobtitle for the person. |
||||
170 | * @return $this |
||||
171 | */ |
||||
172 | public function addJobtitle($jobtitle) |
||||
173 | { |
||||
174 | $this->setProperty( |
||||
175 | 'jobtitle', |
||||
176 | 'TITLE' . $this->getCharsetString(), |
||||
177 | $jobtitle |
||||
178 | ); |
||||
179 | |||||
180 | return $this; |
||||
181 | } |
||||
182 | |||||
183 | /** |
||||
184 | * Add a label |
||||
185 | * |
||||
186 | * @param string $label |
||||
187 | * @param string $type |
||||
188 | * |
||||
189 | * @return $this |
||||
190 | */ |
||||
191 | public function addLabel($label, $type = '') |
||||
192 | { |
||||
193 | $this->setProperty( |
||||
194 | 'label', |
||||
195 | 'LABEL' . ($type !== '' ? ';' . $type : ''), |
||||
196 | $label |
||||
197 | ); |
||||
198 | |||||
199 | return $this; |
||||
200 | } |
||||
201 | |||||
202 | /** |
||||
203 | * Add role |
||||
204 | * |
||||
205 | * @param string $role The role for the person. |
||||
206 | * @return $this |
||||
207 | */ |
||||
208 | public function addRole($role) |
||||
209 | { |
||||
210 | $this->setProperty( |
||||
211 | 'role', |
||||
212 | 'ROLE' . $this->getCharsetString(), |
||||
213 | $role |
||||
214 | ); |
||||
215 | |||||
216 | return $this; |
||||
217 | } |
||||
218 | |||||
219 | /** |
||||
220 | * Add a photo or logo (depending on property name) |
||||
221 | * |
||||
222 | * @param string $property LOGO|PHOTO |
||||
223 | * @param string $url image url or filename |
||||
224 | * @param bool $include Do we include the image in our vcard or not? |
||||
225 | * @param string $element The name of the element to set |
||||
226 | * @throws VCardException |
||||
227 | */ |
||||
228 | private function addMedia($property, $url, $include = true, $element) |
||||
229 | { |
||||
230 | $mimeType = null; |
||||
231 | |||||
232 | //Is this URL for a remote resource? |
||||
233 | if (filter_var($url, FILTER_VALIDATE_URL) !== false) { |
||||
234 | $headers = get_headers($url, 1); |
||||
235 | |||||
236 | if (array_key_exists('Content-Type', $headers)) { |
||||
237 | $mimeType = $headers['Content-Type']; |
||||
238 | if (is_array($mimeType)) { |
||||
239 | $mimeType = end($mimeType); |
||||
240 | } |
||||
241 | } |
||||
242 | } else { |
||||
243 | //Local file, so inspect it directly |
||||
244 | $mimeType = mime_content_type($url); |
||||
245 | } |
||||
246 | if (strpos($mimeType, ';') !== false) { |
||||
247 | $mimeType = strstr($mimeType, ';', true); |
||||
248 | } |
||||
249 | if (!is_string($mimeType) || substr($mimeType, 0, 6) !== 'image/') { |
||||
250 | throw VCardException::invalidImage(); |
||||
251 | } |
||||
252 | $fileType = strtoupper(substr($mimeType, 6)); |
||||
253 | |||||
254 | if ($include) { |
||||
255 | if ((bool) ini_get('allow_url_fopen') === true) { |
||||
256 | $value = file_get_contents($url); |
||||
257 | } else { |
||||
258 | $curl = curl_init(); |
||||
259 | curl_setopt($curl, CURLOPT_URL, $url); |
||||
260 | curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); |
||||
261 | $value = curl_exec($curl); |
||||
262 | curl_close($curl); |
||||
263 | } |
||||
264 | |||||
265 | if (!$value) { |
||||
266 | throw VCardException::emptyURL(); |
||||
267 | } |
||||
268 | |||||
269 | $value = base64_encode($value); |
||||
270 | $property .= ";ENCODING=b;TYPE=" . $fileType; |
||||
271 | } else { |
||||
272 | if (filter_var($url, FILTER_VALIDATE_URL) !== false) { |
||||
273 | $propertySuffix = ';VALUE=URL'; |
||||
274 | $propertySuffix .= ';TYPE=' . strtoupper($fileType); |
||||
275 | |||||
276 | $property = $property . $propertySuffix; |
||||
277 | $value = $url; |
||||
278 | } else { |
||||
279 | $value = $url; |
||||
280 | } |
||||
281 | } |
||||
282 | |||||
283 | $this->setProperty( |
||||
284 | $element, |
||||
285 | $property, |
||||
286 | $value |
||||
287 | ); |
||||
288 | } |
||||
289 | |||||
290 | /** |
||||
291 | * Add a photo or logo (depending on property name) |
||||
292 | * |
||||
293 | * @param string $property LOGO|PHOTO |
||||
294 | * @param string $content image content |
||||
295 | * @param string $element The name of the element to set |
||||
296 | */ |
||||
297 | private function addMediaContent($property, $content, $element) |
||||
298 | { |
||||
299 | $finfo = new \finfo(); |
||||
300 | $mimeType = $finfo->buffer($content, FILEINFO_MIME_TYPE); |
||||
301 | |||||
302 | if (strpos($mimeType, ';') !== false) { |
||||
303 | $mimeType = strstr($mimeType, ';', true); |
||||
304 | } |
||||
305 | if (!is_string($mimeType) || substr($mimeType, 0, 6) !== 'image/') { |
||||
0 ignored issues
–
show
introduced
by
![]() |
|||||
306 | throw VCardException::invalidImage(); |
||||
307 | } |
||||
308 | $fileType = strtoupper(substr($mimeType, 6)); |
||||
309 | |||||
310 | $content = base64_encode($content); |
||||
311 | $property .= ";ENCODING=b;TYPE=" . $fileType; |
||||
312 | |||||
313 | $this->setProperty( |
||||
314 | $element, |
||||
315 | $property, |
||||
316 | $content |
||||
317 | ); |
||||
318 | } |
||||
319 | |||||
320 | /** |
||||
321 | * Add name |
||||
322 | * |
||||
323 | * @param string [optional] $lastName |
||||
324 | * @param string [optional] $firstName |
||||
325 | * @param string [optional] $additional |
||||
326 | * @param string [optional] $prefix |
||||
327 | * @param string [optional] $suffix |
||||
328 | * @return $this |
||||
329 | */ |
||||
330 | public function addName( |
||||
331 | $lastName = '', |
||||
332 | $firstName = '', |
||||
333 | $additional = '', |
||||
334 | $prefix = '', |
||||
335 | $suffix = '' |
||||
336 | ) { |
||||
337 | // define values with non-empty values |
||||
338 | $values = array_filter([ |
||||
339 | $prefix, |
||||
340 | $firstName, |
||||
341 | $additional, |
||||
342 | $lastName, |
||||
343 | $suffix, |
||||
344 | ]); |
||||
345 | |||||
346 | // define filename |
||||
347 | $this->setFilename($values); |
||||
348 | |||||
349 | // set property |
||||
350 | $property = $lastName . ';' . $firstName . ';' . $additional . ';' . $prefix . ';' . $suffix; |
||||
351 | $this->setProperty( |
||||
352 | 'name', |
||||
353 | 'N' . $this->getCharsetString(), |
||||
354 | $property |
||||
355 | ); |
||||
356 | |||||
357 | // is property FN set? |
||||
358 | if (!$this->hasProperty('FN')) { |
||||
359 | // set property |
||||
360 | $this->setProperty( |
||||
361 | 'fullname', |
||||
362 | 'FN' . $this->getCharsetString(), |
||||
363 | trim(implode(' ', $values)) |
||||
364 | ); |
||||
365 | } |
||||
366 | |||||
367 | return $this; |
||||
368 | } |
||||
369 | |||||
370 | /** |
||||
371 | * Add note |
||||
372 | * |
||||
373 | * @param string $note |
||||
374 | * @return $this |
||||
375 | */ |
||||
376 | public function addNote($note) |
||||
377 | { |
||||
378 | $this->setProperty( |
||||
379 | 'note', |
||||
380 | 'NOTE' . $this->getCharsetString(), |
||||
381 | $note |
||||
382 | ); |
||||
383 | |||||
384 | return $this; |
||||
385 | } |
||||
386 | |||||
387 | /** |
||||
388 | * Add categories |
||||
389 | * |
||||
390 | * @param array $categories |
||||
391 | * @return $this |
||||
392 | */ |
||||
393 | public function addCategories($categories) |
||||
394 | { |
||||
395 | $this->setProperty( |
||||
396 | 'categories', |
||||
397 | 'CATEGORIES' . $this->getCharsetString(), |
||||
398 | trim(implode(',', $categories)) |
||||
399 | ); |
||||
400 | |||||
401 | return $this; |
||||
402 | } |
||||
403 | |||||
404 | /** |
||||
405 | * Add phone number |
||||
406 | * |
||||
407 | * @param string $number |
||||
408 | * @param string [optional] $type |
||||
409 | * Type may be PREF | WORK | HOME | VOICE | FAX | MSG | |
||||
410 | * CELL | PAGER | BBS | CAR | MODEM | ISDN | VIDEO |
||||
411 | * or any senseful combination, e.g. "PREF;WORK;VOICE" |
||||
412 | * @return $this |
||||
413 | */ |
||||
414 | public function addPhoneNumber($number, $type = '') |
||||
415 | { |
||||
416 | $this->setProperty( |
||||
417 | 'phoneNumber', |
||||
418 | 'TEL' . (($type != '') ? ';' . $type : ''), |
||||
419 | $number |
||||
420 | ); |
||||
421 | |||||
422 | return $this; |
||||
423 | } |
||||
424 | |||||
425 | /** |
||||
426 | * Add Logo |
||||
427 | * |
||||
428 | * @param string $url image url or filename |
||||
429 | * @param bool $include Include the image in our vcard? |
||||
430 | * @return $this |
||||
431 | */ |
||||
432 | public function addLogo($url, $include = true) |
||||
433 | { |
||||
434 | $this->addMedia( |
||||
435 | 'LOGO', |
||||
436 | $url, |
||||
437 | $include, |
||||
438 | 'logo' |
||||
439 | ); |
||||
440 | |||||
441 | return $this; |
||||
442 | } |
||||
443 | |||||
444 | /** |
||||
445 | * Add Logo content |
||||
446 | * |
||||
447 | * @param string $content image content |
||||
448 | * @return $this |
||||
449 | */ |
||||
450 | public function addLogoContent($content) |
||||
451 | { |
||||
452 | $this->addMediaContent( |
||||
453 | 'LOGO', |
||||
454 | $content, |
||||
455 | 'logo' |
||||
456 | ); |
||||
457 | |||||
458 | return $this; |
||||
459 | } |
||||
460 | |||||
461 | /** |
||||
462 | * Add Photo |
||||
463 | * |
||||
464 | * @param string $url image url or filename |
||||
465 | * @param bool $include Include the image in our vcard? |
||||
466 | * @return $this |
||||
467 | */ |
||||
468 | public function addPhoto($url, $include = true) |
||||
469 | { |
||||
470 | $this->addMedia( |
||||
471 | 'PHOTO', |
||||
472 | $url, |
||||
473 | $include, |
||||
474 | 'photo' |
||||
475 | ); |
||||
476 | |||||
477 | return $this; |
||||
478 | } |
||||
479 | |||||
480 | /** |
||||
481 | * Add Photo content |
||||
482 | * |
||||
483 | * @param string $content image content |
||||
484 | * @return $this |
||||
485 | */ |
||||
486 | public function addPhotoContent($content) |
||||
487 | { |
||||
488 | $this->addMediaContent( |
||||
489 | 'PHOTO', |
||||
490 | $content, |
||||
491 | 'photo' |
||||
492 | ); |
||||
493 | |||||
494 | return $this; |
||||
495 | } |
||||
496 | |||||
497 | /** |
||||
498 | * Add URL |
||||
499 | * |
||||
500 | * @param string $url |
||||
501 | * @param string [optional] $type Type may be WORK | HOME |
||||
502 | * @return $this |
||||
503 | */ |
||||
504 | public function addURL($url, $type = '') |
||||
505 | { |
||||
506 | $this->setProperty( |
||||
507 | 'url', |
||||
508 | 'URL' . (($type != '') ? ';' . $type : ''), |
||||
509 | $url |
||||
510 | ); |
||||
511 | |||||
512 | return $this; |
||||
513 | } |
||||
514 | |||||
515 | /** |
||||
516 | * Build VCard (.vcf) |
||||
517 | * |
||||
518 | * @return string |
||||
519 | */ |
||||
520 | public function buildVCard() |
||||
521 | { |
||||
522 | // init string |
||||
523 | $string = "BEGIN:VCARD\r\n"; |
||||
524 | $string .= "VERSION:3.0\r\n"; |
||||
525 | $string .= "REV:" . date("Y-m-d") . "T" . date("H:i:s") . "Z\r\n"; |
||||
526 | |||||
527 | // loop all properties |
||||
528 | $properties = $this->getProperties(); |
||||
529 | foreach ($properties as $property) { |
||||
530 | // add to string |
||||
531 | $string .= $this->fold($property['key'] . ':' . $this->escape($property['value']) . "\r\n"); |
||||
532 | } |
||||
533 | |||||
534 | // add to string |
||||
535 | $string .= "END:VCARD\r\n"; |
||||
536 | |||||
537 | // 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. ![]() |
|||||
538 | return $string; |
||||
539 | } |
||||
540 | |||||
541 | /** |
||||
542 | * Build VCalender (.ics) - Safari (< iOS 8) can not open .vcf files, so we have build a workaround. |
||||
543 | * |
||||
544 | * @return string |
||||
545 | */ |
||||
546 | public function buildVCalendar() |
||||
547 | { |
||||
548 | // init dates |
||||
549 | $dtstart = date("Ymd") . "T" . date("Hi") . "00"; |
||||
550 | $dtend = date("Ymd") . "T" . date("Hi") . "01"; |
||||
551 | |||||
552 | // init string |
||||
553 | $string = "BEGIN:VCALENDAR\n"; |
||||
554 | $string .= "VERSION:2.0\n"; |
||||
555 | $string .= "BEGIN:VEVENT\n"; |
||||
556 | $string .= "DTSTART;TZID=Europe/London:" . $dtstart . "\n"; |
||||
557 | $string .= "DTEND;TZID=Europe/London:" . $dtend . "\n"; |
||||
558 | $string .= "SUMMARY:Click attached contact below to save to your contacts\n"; |
||||
559 | $string .= "DTSTAMP:" . $dtstart . "Z\n"; |
||||
560 | $string .= "ATTACH;VALUE=BINARY;ENCODING=BASE64;FMTTYPE=text/directory;\n"; |
||||
561 | $string .= " X-APPLE-FILENAME=" . $this->getFilename() . "." . $this->getFileExtension() . ":\n"; |
||||
562 | |||||
563 | // base64 encode it so that it can be used as an attachemnt to the "dummy" calendar appointment |
||||
564 | $b64vcard = base64_encode($this->buildVCard()); |
||||
565 | |||||
566 | // chunk the single long line of b64 text in accordance with RFC2045 |
||||
567 | // (and the exact line length determined from the original .ics file exported from Apple calendar |
||||
568 | $b64mline = chunk_split($b64vcard, 74, "\n"); |
||||
569 | |||||
570 | // need to indent all the lines by 1 space for the iphone (yes really?!!) |
||||
571 | $b64final = preg_replace('/(.+)/', ' $1', $b64mline); |
||||
572 | $string .= $b64final; |
||||
573 | |||||
574 | // output the correctly formatted encoded text |
||||
575 | $string .= "END:VEVENT\n"; |
||||
576 | $string .= "END:VCALENDAR\n"; |
||||
577 | |||||
578 | // 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. ![]() |
|||||
579 | return $string; |
||||
580 | } |
||||
581 | |||||
582 | /** |
||||
583 | * Returns the browser user agent string. |
||||
584 | * |
||||
585 | * @return string |
||||
586 | */ |
||||
587 | protected function getUserAgent() |
||||
588 | { |
||||
589 | if (array_key_exists('HTTP_USER_AGENT', $_SERVER)) { |
||||
590 | $browser = strtolower($_SERVER['HTTP_USER_AGENT']); |
||||
591 | } else { |
||||
592 | $browser = 'unknown'; |
||||
593 | } |
||||
594 | |||||
595 | return $browser; |
||||
596 | } |
||||
597 | |||||
598 | /** |
||||
599 | * Decode |
||||
600 | * |
||||
601 | * @param string $value The value to decode |
||||
602 | * @return string decoded |
||||
603 | */ |
||||
604 | private function decode($value) |
||||
605 | { |
||||
606 | // convert cyrlic, greek or other caracters to ASCII characters |
||||
607 | return Transliterator::transliterate($value); |
||||
608 | } |
||||
609 | |||||
610 | /** |
||||
611 | * Download a vcard or vcal file to the browser. |
||||
612 | */ |
||||
613 | public function download() |
||||
614 | { |
||||
615 | // define output |
||||
616 | $output = $this->getOutput(); |
||||
617 | |||||
618 | foreach ($this->getHeaders(false) as $header) { |
||||
619 | header($header); |
||||
620 | } |
||||
621 | |||||
622 | // echo the output and it will be a download |
||||
623 | echo $output; |
||||
624 | } |
||||
625 | |||||
626 | /** |
||||
627 | * Fold a line according to RFC2425 section 5.8.1. |
||||
628 | * |
||||
629 | * @link http://tools.ietf.org/html/rfc2425#section-5.8.1 |
||||
630 | * @param string $text |
||||
631 | * @return mixed |
||||
632 | */ |
||||
633 | protected function fold($text) |
||||
634 | { |
||||
635 | if (strlen($text) <= 75) { |
||||
636 | return $text; |
||||
637 | } |
||||
638 | |||||
639 | // split, wrap and trim trailing separator |
||||
640 | return substr($this->chunk_split_unicode($text, 75, "\r\n "), 0, -3); |
||||
641 | } |
||||
642 | |||||
643 | /** |
||||
644 | * multibyte word chunk split |
||||
645 | * @link http://php.net/manual/en/function.chunk-split.php#107711 |
||||
646 | * |
||||
647 | * @param string $body The string to be chunked. |
||||
648 | * @param integer $chunklen The chunk length. |
||||
649 | * @param string $end The line ending sequence. |
||||
650 | * @return string Chunked string |
||||
651 | */ |
||||
652 | protected function chunk_split_unicode($body, $chunklen = 76, $end = "\r\n") |
||||
653 | { |
||||
654 | $array = array_chunk( |
||||
655 | preg_split("//u", $body, -1, PREG_SPLIT_NO_EMPTY), $chunklen); |
||||
0 ignored issues
–
show
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
![]() |
|||||
656 | $body = ""; |
||||
657 | foreach ($array as $item) { |
||||
658 | $body .= join("", $item) . $end; |
||||
659 | } |
||||
660 | return $body; |
||||
661 | } |
||||
662 | |||||
663 | /** |
||||
664 | * Escape newline characters according to RFC2425 section 5.8.4. |
||||
665 | * |
||||
666 | * @link http://tools.ietf.org/html/rfc2425#section-5.8.4 |
||||
667 | * @param string $text |
||||
668 | * @return string |
||||
669 | */ |
||||
670 | protected function escape($text) |
||||
671 | { |
||||
672 | $text = str_replace("\r\n", "\\n", $text); |
||||
673 | $text = str_replace("\n", "\\n", $text); |
||||
674 | |||||
675 | return $text; |
||||
676 | } |
||||
677 | |||||
678 | /** |
||||
679 | * Get output as string |
||||
680 | * @deprecated in the future |
||||
681 | * |
||||
682 | * @return string |
||||
683 | */ |
||||
684 | public function get() |
||||
685 | { |
||||
686 | return $this->getOutput(); |
||||
687 | } |
||||
688 | |||||
689 | /** |
||||
690 | * Get charset |
||||
691 | * |
||||
692 | * @return string |
||||
693 | */ |
||||
694 | public function getCharset() |
||||
695 | { |
||||
696 | return $this->charset; |
||||
697 | } |
||||
698 | |||||
699 | /** |
||||
700 | * Get charset string |
||||
701 | * |
||||
702 | * @return string |
||||
703 | */ |
||||
704 | public function getCharsetString() |
||||
705 | { |
||||
706 | return ';CHARSET=' . $this->charset; |
||||
707 | } |
||||
708 | |||||
709 | /** |
||||
710 | * Get content type |
||||
711 | * |
||||
712 | * @return string |
||||
713 | */ |
||||
714 | public function getContentType() |
||||
715 | { |
||||
716 | return ($this->isIOS7()) ? |
||||
717 | 'text/x-vcalendar' : 'text/x-vcard'; |
||||
718 | } |
||||
719 | |||||
720 | /** |
||||
721 | * Get filename |
||||
722 | * |
||||
723 | * @return string |
||||
724 | */ |
||||
725 | public function getFilename() |
||||
726 | { |
||||
727 | if (!$this->filename) { |
||||
728 | return 'unknown'; |
||||
729 | } |
||||
730 | |||||
731 | return $this->filename; |
||||
732 | } |
||||
733 | |||||
734 | /** |
||||
735 | * Get file extension |
||||
736 | * |
||||
737 | * @return string |
||||
738 | */ |
||||
739 | public function getFileExtension() |
||||
740 | { |
||||
741 | return ($this->isIOS7()) ? |
||||
742 | 'ics' : 'vcf'; |
||||
743 | } |
||||
744 | |||||
745 | /** |
||||
746 | * Get headers |
||||
747 | * |
||||
748 | * @param bool $asAssociative |
||||
749 | * @return array |
||||
750 | */ |
||||
751 | public function getHeaders($asAssociative) |
||||
752 | { |
||||
753 | $contentType = $this->getContentType() . '; charset=' . $this->getCharset(); |
||||
754 | $contentDisposition = 'attachment; filename=' . $this->getFilename() . '.' . $this->getFileExtension(); |
||||
755 | $contentLength = mb_strlen($this->getOutput(), '8bit'); |
||||
756 | $connection = 'close'; |
||||
757 | |||||
758 | if ((bool)$asAssociative) { |
||||
759 | return [ |
||||
760 | 'Content-type' => $contentType, |
||||
761 | 'Content-Disposition' => $contentDisposition, |
||||
762 | 'Content-Length' => $contentLength, |
||||
763 | 'Connection' => $connection, |
||||
764 | ]; |
||||
765 | } |
||||
766 | |||||
767 | return [ |
||||
768 | 'Content-type: ' . $contentType, |
||||
769 | 'Content-Disposition: ' . $contentDisposition, |
||||
770 | 'Content-Length: ' . $contentLength, |
||||
771 | 'Connection: ' . $connection, |
||||
772 | ]; |
||||
773 | } |
||||
774 | |||||
775 | /** |
||||
776 | * Get output as string |
||||
777 | * iOS devices (and safari < iOS 8 in particular) can not read .vcf (= vcard) files. |
||||
778 | * So I build a workaround to build a .ics (= vcalender) file. |
||||
779 | * |
||||
780 | * @return string |
||||
781 | */ |
||||
782 | public function getOutput() |
||||
783 | { |
||||
784 | $output = ($this->isIOS7()) ? |
||||
785 | $this->buildVCalendar() : $this->buildVCard(); |
||||
786 | |||||
787 | return $output; |
||||
788 | } |
||||
789 | |||||
790 | /** |
||||
791 | * Get properties |
||||
792 | * |
||||
793 | * @return array |
||||
794 | */ |
||||
795 | public function getProperties() |
||||
796 | { |
||||
797 | return $this->properties; |
||||
798 | } |
||||
799 | |||||
800 | /** |
||||
801 | * Has property |
||||
802 | * |
||||
803 | * @param string $key |
||||
804 | * @return bool |
||||
805 | */ |
||||
806 | public function hasProperty($key) |
||||
807 | { |
||||
808 | $properties = $this->getProperties(); |
||||
809 | |||||
810 | foreach ($properties as $property) { |
||||
811 | if ($property['key'] === $key && $property['value'] !== '') { |
||||
812 | return true; |
||||
813 | } |
||||
814 | } |
||||
815 | |||||
816 | return false; |
||||
817 | } |
||||
818 | |||||
819 | /** |
||||
820 | * Is iOS - Check if the user is using an iOS-device |
||||
821 | * |
||||
822 | * @return bool |
||||
823 | */ |
||||
824 | public function isIOS() |
||||
825 | { |
||||
826 | // get user agent |
||||
827 | $browser = $this->getUserAgent(); |
||||
828 | |||||
829 | return (strpos($browser, 'iphone') || strpos($browser, 'ipod') || strpos($browser, 'ipad')); |
||||
830 | } |
||||
831 | |||||
832 | /** |
||||
833 | * Is iOS less than 7 (should cal wrapper be returned) |
||||
834 | * |
||||
835 | * @return bool |
||||
836 | */ |
||||
837 | public function isIOS7() |
||||
838 | { |
||||
839 | return ($this->isIOS() && $this->shouldAttachmentBeCal()); |
||||
840 | } |
||||
841 | |||||
842 | /** |
||||
843 | * Save to a file |
||||
844 | * |
||||
845 | * @return void |
||||
846 | */ |
||||
847 | public function save() |
||||
848 | { |
||||
849 | $file = $this->getFilename() . '.' . $this->getFileExtension(); |
||||
850 | |||||
851 | // Add save path if given |
||||
852 | if (null !== $this->savePath) { |
||||
853 | $file = $this->savePath . $file; |
||||
854 | } |
||||
855 | |||||
856 | file_put_contents( |
||||
857 | $file, |
||||
858 | $this->getOutput() |
||||
859 | ); |
||||
860 | } |
||||
861 | |||||
862 | /** |
||||
863 | * Set charset |
||||
864 | * |
||||
865 | * @param mixed $charset |
||||
866 | * @return void |
||||
867 | */ |
||||
868 | public function setCharset($charset) |
||||
869 | { |
||||
870 | $this->charset = $charset; |
||||
871 | } |
||||
872 | |||||
873 | /** |
||||
874 | * Set filename |
||||
875 | * |
||||
876 | * @param mixed $value |
||||
877 | * @param bool $overwrite [optional] Default overwrite is true |
||||
878 | * @param string $separator [optional] Default separator is an underscore '_' |
||||
879 | * @return void |
||||
880 | */ |
||||
881 | public function setFilename($value, $overwrite = true, $separator = '_') |
||||
882 | { |
||||
883 | // recast to string if $value is array |
||||
884 | if (is_array($value)) { |
||||
885 | $value = implode($separator, $value); |
||||
886 | } |
||||
887 | |||||
888 | // trim unneeded values |
||||
889 | $value = trim($value, $separator); |
||||
890 | |||||
891 | // remove all spaces |
||||
892 | $value = preg_replace('/\s+/', $separator, $value); |
||||
893 | |||||
894 | // if value is empty, stop here |
||||
895 | if (empty($value)) { |
||||
896 | return; |
||||
897 | } |
||||
898 | |||||
899 | // decode value + lowercase the string |
||||
900 | $value = strtolower($this->decode($value)); |
||||
901 | |||||
902 | // urlize this part |
||||
903 | $value = Transliterator::urlize($value); |
||||
904 | |||||
905 | // overwrite filename or add to filename using a prefix in between |
||||
906 | $this->filename = ($overwrite) ? |
||||
907 | $value : $this->filename . $separator . $value; |
||||
908 | } |
||||
909 | |||||
910 | /** |
||||
911 | * Set the save path directory |
||||
912 | * |
||||
913 | * @param string $savePath Save Path |
||||
914 | * @throws VCardException |
||||
915 | */ |
||||
916 | public function setSavePath($savePath) |
||||
917 | { |
||||
918 | if (!is_dir($savePath)) { |
||||
919 | throw VCardException::outputDirectoryNotExists(); |
||||
920 | } |
||||
921 | |||||
922 | // Add trailing directory separator the save path |
||||
923 | if (substr($savePath, -1) != DIRECTORY_SEPARATOR) { |
||||
924 | $savePath .= DIRECTORY_SEPARATOR; |
||||
925 | } |
||||
926 | |||||
927 | $this->savePath = $savePath; |
||||
928 | } |
||||
929 | |||||
930 | /** |
||||
931 | * Set property |
||||
932 | * |
||||
933 | * @param string $element The element name you want to set, f.e.: name, email, phoneNumber, ... |
||||
934 | * @param string $key |
||||
935 | * @param string $value |
||||
936 | * @throws VCardException |
||||
937 | */ |
||||
938 | private function setProperty($element, $key, $value) |
||||
939 | { |
||||
940 | if (!in_array($element, $this->multiplePropertiesForElementAllowed) |
||||
941 | && isset($this->definedElements[$element]) |
||||
942 | ) { |
||||
943 | throw VCardException::elementAlreadyExists($element); |
||||
944 | } |
||||
945 | |||||
946 | // we define that we set this element |
||||
947 | $this->definedElements[$element] = true; |
||||
948 | |||||
949 | // adding property |
||||
950 | $this->properties[] = [ |
||||
951 | 'key' => $key, |
||||
952 | 'value' => $value |
||||
953 | ]; |
||||
954 | } |
||||
955 | |||||
956 | /** |
||||
957 | * Checks if we should return vcard in cal wrapper |
||||
958 | * |
||||
959 | * @return bool |
||||
960 | */ |
||||
961 | protected function shouldAttachmentBeCal() |
||||
962 | { |
||||
963 | $browser = $this->getUserAgent(); |
||||
964 | |||||
965 | $matches = []; |
||||
966 | preg_match('/os (\d+)_(\d+)\s+/', $browser, $matches); |
||||
967 | $version = isset($matches[1]) ? ((int)$matches[1]) : 999; |
||||
968 | |||||
969 | return ($version < 8); |
||||
970 | } |
||||
971 | } |
||||
972 |