Commit | Line | Data |
---|---|---|
eda18149 FB |
1 | <?php |
2 | /*************************************************************************** | |
2ab75571 | 3 | * Copyright (C) 2003-2010 Polytechnique.org * |
eda18149 FB |
4 | * http://opensource.polytechnique.org/ * |
5 | * * | |
6 | * This program is free software; you can redistribute it and/or modify * | |
7 | * it under the terms of the GNU General Public License as published by * | |
8 | * the Free Software Foundation; either version 2 of the License, or * | |
9 | * (at your option) any later version. * | |
10 | * * | |
11 | * This program is distributed in the hope that it will be useful, * | |
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * | |
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * | |
14 | * GNU General Public License for more details. * | |
15 | * * | |
16 | * You should have received a copy of the GNU General Public License * | |
17 | * along with this program; if not, write to the Free Software * | |
18 | * Foundation, Inc., * | |
19 | * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * | |
20 | **************************************************************************/ | |
21 | ||
22 | /** Describe a field. | |
23 | */ | |
24 | class PlVcardField | |
25 | { | |
26 | /** Default encoding for the different fields. | |
27 | */ | |
28 | private static $defaultEncoding = array('BEGIN' => 'limited', | |
29 | 'END' => 'limited', | |
30 | 'SOURCE' => 'uri', | |
31 | 'NAME' => 'text', | |
32 | 'PROFILE' => 'limited', | |
33 | 'FN' => 'text', | |
34 | 'N' => 'structured', | |
35 | 'NICKNAME' => 'text*', | |
36 | 'PHOTO' => 'binary', | |
37 | 'BDAY' => 'date', | |
38 | 'ADR' => 'structured', | |
39 | 'LABEL' => 'text', | |
40 | 'TEL' => 'phone-number', | |
41 | 'EMAIL' => 'text', | |
42 | 'MAILER' => 'text', | |
43 | 'TZ' => 'utc-offset', | |
44 | 'GEO' => 'structured', | |
45 | 'TITLE' => 'text', | |
46 | 'ROLE' => 'text', | |
47 | 'LOGO' => 'binary', | |
48 | 'AGENT' => 'vcard', | |
49 | 'ORG' => 'structured', | |
50 | 'CATEGORIES' => 'text*', | |
51 | 'NOTE' => 'text', | |
52 | 'PRODID' => 'text', | |
53 | 'REV' => 'date-time', | |
54 | 'SORT-STRING' => 'text', | |
55 | 'SOUND' => 'binary', | |
56 | 'UID' => 'text', | |
57 | 'URL' => 'uri', | |
58 | 'VERSION' => 'limited', | |
59 | 'CLASS' => 'text', | |
60 | 'KEY' => 'binary'); | |
61 | ||
62 | /** Field group. | |
63 | */ | |
64 | public $group = null; | |
65 | ||
66 | /** Field name. | |
67 | */ | |
68 | public $name = null; | |
69 | ||
70 | /** Field value. | |
71 | */ | |
72 | public $value = null; | |
73 | ||
74 | ||
75 | /* RFC2425 parameters */ | |
76 | ||
77 | /** ENCODING: encoding of the field. | |
78 | * default is 8bit, only 'b' is supported for binary fields | |
79 | */ | |
80 | public $ENCODING = null; | |
81 | ||
82 | /** VALUE: type of the value of the field. | |
83 | * available types from RFC2425 are: | |
84 | * -uri: one uri | |
85 | * -text*: one or more text entry | |
86 | * -date*: one or more date entry | |
87 | * -time*: one or more time entry | |
88 | * -date-time: one or more date-time entry | |
89 | * -integer: one or more integer | |
90 | * -boolean: (TRUE|FALSE) | |
91 | * -float: one ore more float entry | |
92 | * -x-username: user-defined type | |
93 | * -(iana-token) | |
94 | * | |
95 | * available types from RFC2426 are: | |
96 | * -binary: (encoding type must be specified) | |
97 | * -vcard: inlined vcard (encoded as text) | |
98 | * -phone-number | |
99 | * -utc-offset | |
100 | * -structured | |
101 | */ | |
102 | public $VALUE = null; | |
103 | ||
104 | /** CHARSET: charset of the value of the field. | |
105 | */ | |
106 | public $CHARSET = null; | |
107 | ||
108 | /** LANGUAGE: lanugage of the field. | |
109 | */ | |
110 | public $LANGUAGE = null; | |
111 | ||
112 | /** CONTEXT: context of the value. | |
113 | */ | |
114 | public $CONTEXT = null; | |
115 | ||
116 | ||
117 | /* RFC2426 parameters */ | |
118 | ||
119 | /** TYPE: variants of the type. | |
120 | */ | |
121 | public $TYPE = null; | |
122 | ||
123 | ||
124 | public function __construct($group, $name, $value) | |
125 | { | |
126 | $this->group = $group; | |
127 | $this->name = $name; | |
128 | $this->value = $value; | |
129 | ||
130 | $type = @self::$defaultEncoding[$name]; | |
131 | if (is_null($type)) { | |
132 | $type = 'text'; | |
133 | } | |
134 | if ($type == 'binary') { | |
135 | $this->ENCODING = 'b'; | |
136 | } else if ($type == 'text' || $type == 'text*' || $type == 'structured') { | |
137 | $this->CHARSET = PlVCard::$charset; | |
138 | } | |
139 | } | |
140 | ||
141 | public function show() | |
142 | { | |
143 | $params = array(); | |
144 | foreach ($this as $pk => $pv) { | |
145 | if ($pk != 'value' && $pk != 'group' && $pk != 'name') { | |
146 | if ($pv instanceof PlFlagset) { | |
147 | $params[$pk] = $pv->flags(); | |
148 | } else if (!is_null($pv)) { | |
149 | $params[$pk] = $pv; | |
150 | } | |
151 | } | |
152 | } | |
153 | $encoding = $this->VALUE; | |
154 | if (is_null($encoding)) { | |
155 | $encoding = @self::$defaultEncoding[$this->name]; | |
156 | } | |
157 | if (is_null($encoding)) { | |
158 | // let say default encoding is 'text' | |
159 | $encoding = 'text'; | |
160 | } | |
161 | self::output($this->group, $this->name, $params, self::format($this->value, $encoding)); | |
162 | } | |
163 | ||
164 | static public function format($value, $format) | |
165 | { | |
166 | if (substr($format, -1) == '*') { | |
167 | $format = substr($format, 0, -1); | |
168 | if (is_array($value)) { | |
169 | $vals = array(); | |
170 | foreach ($value as $v) { | |
171 | $vals[] = self::format($v, $format); | |
172 | } | |
173 | return implode(',', $vals); | |
174 | } | |
175 | } | |
176 | if (is_null($value)) { | |
177 | return ''; | |
178 | } | |
179 | switch ($format) { | |
180 | case 'float': | |
181 | return str_replace(',', '.', $value); | |
182 | ||
183 | case 'boolean': | |
184 | if ($value == 'TRUE' || $value == 'FALSE') { | |
185 | return $value; | |
186 | } | |
187 | return $value ? 'TRUE' : 'FALSE'; | |
188 | ||
189 | case 'binary': | |
190 | if (!PlVCard::$escapeBinary) { | |
191 | return base64_encode($value); | |
192 | } | |
193 | $value = base64_encode($value); | |
194 | ||
195 | case 'limited': | |
196 | case 'vcard': | |
197 | case 'text': | |
198 | if (PlVCard::$charset != 'UTF-8' && $format != 'binary') { | |
199 | $value = iconv('UTF-8', PlVCard::$charset, $value); | |
200 | } | |
201 | return str_replace(array('\\', ',', "\r\n", "\r", "\n"), | |
202 | array('\\\\', '\\,', '\\n', '\\n', '\\n'), | |
203 | $value); | |
204 | ||
205 | case 'structured': | |
206 | $vals = array(); | |
207 | foreach ($value as $k => $v) { | |
208 | if ($k{0} == '_') { | |
209 | continue; | |
210 | } | |
211 | $enc = isset($value->_encoding[$k]) ? $value->_encoding[$k] : $value->_encoding['@@EXTRA@@']; | |
212 | $vals[] = str_replace(';', '\\;', self::format($v, $enc)); | |
213 | } | |
214 | return implode(';', $vals); | |
215 | ||
216 | case 'uri': | |
217 | case 'phone-number': | |
218 | case 'utc-offset': | |
219 | case 'integer': | |
220 | default: | |
221 | return $value; | |
222 | } | |
223 | } | |
224 | ||
225 | static public function output($group, $name, $params, $value) | |
226 | { | |
227 | $str = ''; | |
228 | if (!is_null($group)) { | |
229 | $str .= $group . '.'; | |
230 | } | |
231 | $str .= $name; | |
232 | if (!is_null($params)) { | |
233 | foreach ($params as $pn => $pv) { | |
234 | $str .= ';' . $pn . '=' . $pv; | |
235 | } | |
236 | } | |
237 | $str .= ':' . $value; | |
238 | ||
239 | // Folding | |
240 | if (PlVCard::$folding && strlen($str) > 75) { | |
241 | $str = chunk_split($str, 75, "\r\n "); | |
242 | if (substr($str, -3) == "\r\n ") { | |
243 | $str = substr($str, 0, -3); | |
244 | } | |
245 | } | |
246 | echo $str . "\r\n"; | |
247 | } | |
248 | } | |
249 | ||
250 | ||
251 | /** Structure of the N type as described in RFC2426. | |
252 | */ | |
253 | class N_Field | |
254 | { | |
255 | public $_encoding = array('familyName' => 'text*', | |
256 | 'givenName' => 'text*', | |
257 | 'additionalName' => 'text*', | |
258 | 'honorificPrefixes' => 'text*', | |
259 | 'honorificSuffixes' => 'text*'); | |
260 | ||
261 | /** The family name | |
262 | * -type: text-list | |
263 | */ | |
264 | public $familyName = null; | |
265 | ||
266 | /** The given name | |
267 | * -type: text-list | |
268 | */ | |
269 | public $givenName = null; | |
270 | ||
271 | /** The additional names | |
272 | * -type: text-list | |
273 | */ | |
274 | public $additionalName = null; | |
275 | ||
276 | /** Honorific prefixes | |
277 | * -type: text-list | |
278 | */ | |
279 | public $honorificPrefixes = null; | |
280 | ||
281 | /** Honorific suffixes | |
282 | * -type: text-list | |
283 | */ | |
284 | public $honorificSuffixes = null; | |
285 | ||
286 | public function __construct($family, $given, $additional, $prefix, $suffix) | |
287 | { | |
288 | $this->familyName = $family; | |
289 | $this->givenName = $given; | |
290 | $this->additionalName = $additional; | |
291 | ||
292 | $this->honorificPrefixes = $prefix; | |
293 | $this->honorificSuffixes = $suffix; | |
294 | } | |
295 | } | |
296 | ||
297 | ||
298 | /** Structure of the ADR type as described in RFC2426. | |
299 | */ | |
300 | class ADR_Field | |
301 | { | |
302 | public $_encoding = array('postOfficeBox' => 'text', | |
303 | 'extendedAddress' => 'text*', | |
304 | 'streetAddress' => 'text*', | |
305 | 'locality' => 'text', | |
306 | 'region' => 'text', | |
307 | 'postalCode' => 'text', | |
308 | 'countryName' => 'text'); | |
309 | ||
310 | /** The post office box | |
311 | * -type: text | |
312 | */ | |
313 | public $postOfficeBox = null; | |
314 | ||
315 | /** Extended address. | |
316 | * -type: text | |
317 | */ | |
318 | public $extendedAddress = null; | |
319 | ||
320 | /** Street address. | |
321 | * -type: text | |
322 | */ | |
323 | public $streetAddress = null; | |
324 | ||
325 | /** Locality name. | |
326 | * -type: text | |
327 | */ | |
328 | public $locality = null; | |
329 | ||
330 | /** Region name. | |
331 | * -type: text | |
332 | */ | |
333 | public $region = null; | |
334 | ||
335 | /** Postal code. | |
336 | * -type: text | |
337 | */ | |
338 | public $postalCode = null; | |
339 | ||
340 | /** Country name. | |
341 | * -type: text | |
342 | */ | |
343 | public $countryName = null; | |
344 | ||
345 | ||
346 | public function __construct($box, $extend, $street, $locality, $region, | |
347 | $postcode, $country) { | |
348 | $this->postOfficeBox = $box; | |
349 | $this->extendedAddress = $extend; | |
350 | $this->streetAddress = $street; | |
351 | $this->locality = $locality; | |
352 | $this->region = $region; | |
353 | $this->postalCode = $postcode; | |
354 | $this->countryName = $country; | |
355 | } | |
356 | } | |
357 | ||
358 | /** Structure of the ORG type as described in RFC2426. | |
359 | */ | |
360 | class ORG_Field | |
361 | { | |
362 | public $_encoding = array('name' => 'text', | |
363 | '@@EXTRA@@' => 'text'); | |
364 | ||
365 | ||
366 | /** Organisation name. | |
367 | * -type: text | |
368 | */ | |
369 | public $name = null; | |
370 | ||
371 | /** Unit level | |
372 | * -type: several entries | |
373 | * | |
374 | * Use dynamic PHP members to distinguish | |
375 | * multi-fields from text-list. | |
376 | */ | |
377 | ||
378 | public function __construct($org, $units) | |
379 | { | |
380 | $this->name = $org; | |
381 | if (!is_null($units)) { | |
382 | if (is_array($units)) { | |
383 | foreach ($units as $k => $v) { | |
384 | $f = 'unit_' . $k; | |
385 | $this->$f = $v; | |
386 | } | |
387 | } else { | |
388 | $this->unit_0 = $units; | |
389 | } | |
390 | } | |
391 | } | |
392 | } | |
393 | ||
394 | /** Structure of the GEO type as described in RFC2426. | |
395 | */ | |
396 | class GEO_Field | |
397 | { | |
398 | public $_encoding = array('latitude' => 'float', | |
399 | 'longitude' => 'float'); | |
400 | ||
401 | /** Latitude. | |
402 | * -type: float | |
403 | */ | |
404 | public $latitude; | |
405 | ||
406 | /** Longitude. | |
407 | * -type: float | |
408 | */ | |
409 | public $longitude; | |
410 | ||
411 | ||
412 | public function __construct($lat, $lon) | |
413 | { | |
414 | $this->latitude = $lat; | |
415 | $this->longitude = $lon; | |
416 | } | |
417 | } | |
418 | ||
419 | class PlVCardEntry | |
420 | { | |
421 | /* RFC2425 fields */ | |
422 | ||
423 | /** SOURCE: source of the vCard. | |
424 | * -type: uri | |
425 | * -optional | |
426 | */ | |
427 | public $SOURCE = null; | |
428 | ||
429 | /** NAME: name of the entry. | |
430 | * -type: text | |
431 | * -optional | |
432 | */ | |
433 | public $NAME = null; | |
434 | ||
435 | /** PROFILE: profile type. | |
436 | * -type: a registered profile name (vCard) | |
437 | * -optional | |
438 | */ | |
439 | public $PROFILE = null; | |
440 | ||
441 | /* RFC2426 fields */ | |
442 | ||
443 | /* Identification fields */ | |
444 | ||
445 | /** FN: Formatted name. | |
446 | * -type: text | |
447 | * -mandatory | |
448 | */ | |
449 | public $FN = null; | |
450 | ||
451 | /** N: Name structure. | |
452 | * -type: n structure | |
453 | * -mandatory | |
454 | */ | |
455 | public $N = null; | |
456 | ||
457 | /** NICKNAME: List of nick names. | |
458 | * -type: text-list | |
459 | */ | |
460 | public $NICKNAME = null; | |
461 | ||
462 | /** PHOTO: Photo of the object identified by the vcard. | |
463 | * -type: binary, can be reset to URL | |
464 | */ | |
465 | public $PHOTO = null; | |
466 | ||
467 | /** BDAY: Birthday | |
468 | * -type: date, can be reset to date-time | |
469 | */ | |
470 | public $BDAY = null; | |
471 | ||
472 | ||
473 | /* Delivery addressing */ | |
474 | ||
475 | /** ADR: delivery address by components. | |
476 | * -type: adr structure | |
477 | * -variant flags: dom, intl, postal, parcel, home, work, pref (default: intl,postal,parcel,work) | |
478 | */ | |
479 | public $ADR = array(); | |
480 | ||
481 | /** LABEL: formatted text representing a delivery address. | |
482 | * -type: text | |
483 | * -variant flags: dom, intl, postal, parcel, home, work, pref (default: intl,postal,parcel,work) | |
484 | */ | |
485 | public $LABEL = array(); | |
486 | ||
487 | ||
488 | /* Telecommunication addressing */ | |
489 | ||
490 | /** TEL: telephone number. | |
491 | * -type: phone-number | |
492 | * -variant flags: home, msg, work, pref, voice, fax, cell, video, pager, bbs, modem, car, isdn, pcs (default: voice) | |
493 | */ | |
494 | public $TEL = array(); | |
495 | ||
496 | /** EMAIL: electroning mail address. | |
497 | * -type: text | |
498 | * -variant flags: internet, x400, pref (default: internet) | |
499 | */ | |
500 | public $EMAIL = array(); | |
501 | ||
502 | /** MAILER: type of mailer used... | |
503 | * -type: text | |
504 | */ | |
505 | public $MAILER = array(); | |
506 | ||
507 | ||
508 | /* Geographical */ | |
509 | ||
510 | /** TZ: timezone. | |
511 | * -type: utc-offset (can be reset to a text value) | |
512 | */ | |
513 | public $TZ = null; | |
514 | ||
515 | /** GEO: Geographical coordinates. | |
516 | * -type: geo structure | |
517 | */ | |
518 | public $GEO = null; | |
519 | ||
520 | ||
521 | /* Organizational */ | |
522 | ||
523 | /** TITLE: job title, functional position or function. | |
524 | * -type: text | |
525 | */ | |
526 | public $TITLE = array(); | |
527 | ||
528 | /** ROLE: role, occupation, business category. | |
529 | * -type: text | |
530 | */ | |
531 | public $ROLE = array(); | |
532 | ||
533 | /** LOGO: logo of the organization. | |
534 | * -type: binary (can be reset to uri) | |
535 | */ | |
536 | public $LOGO = array(); | |
537 | ||
538 | /** AGENT: define information about another person. | |
539 | * -type: vcard (can be reset to a uri) | |
540 | */ | |
541 | public $AGENT = array(); | |
542 | ||
543 | /** ORG: Organizational name and units. | |
544 | * -type: org structure | |
545 | */ | |
546 | public $ORG = array(); | |
547 | ||
548 | ||
549 | /* Explanatory */ | |
550 | ||
551 | /** CATEGORIES: list of categories. | |
552 | * -type: text-list | |
553 | */ | |
554 | public $CATEGORIES = null; | |
555 | ||
556 | /** NOTE: supplemental information or comment. | |
557 | * -type: text | |
558 | */ | |
559 | public $NOTE = null; | |
560 | ||
561 | /** PRODID: Identifier of the product that created the card. | |
562 | * -type: text (ISO 9070) | |
563 | */ | |
564 | public $PRODID = null; | |
565 | ||
566 | /** REV: revision information about the card. | |
567 | * -type: date-time (can be reset to a simple date) | |
568 | */ | |
569 | public $REV = null; | |
570 | ||
571 | /** SORT-STRING: informations on how to sort this card | |
572 | * -type: text | |
573 | */ | |
574 | public $SORT_STRING = null; | |
575 | ||
576 | /** SOUND: digital sound content that annotates the card. | |
577 | * -type: binary (can be reset to a uri) | |
578 | */ | |
579 | public $SOUND = null; | |
580 | ||
581 | /** UID: globaly unique identifier corresponding to the object. | |
582 | * -type: text | |
583 | * -variant: IANA standard format identifier (optionnal) | |
584 | */ | |
585 | public $UID = null; | |
586 | ||
587 | /** URL: url describing the object the vcard refers to. | |
588 | * -type: uri | |
589 | */ | |
590 | public $URL = null; | |
591 | ||
592 | /** VERSION: format version of the vcard | |
593 | * -type: text | |
594 | * MUST BE "3.0" | |
595 | */ | |
596 | public $VERSION = null; | |
597 | ||
598 | ||
599 | /* Security types */ | |
600 | ||
601 | /** CLASS: access classification. | |
602 | * -type: text (eg.: PUBLIC, PRIVATE, CONFIDENTIAL...) | |
603 | */ | |
604 | public $CLASS = null; | |
605 | ||
606 | /** KEY: public key or authentication certificate associated with the object. | |
607 | * -type: binary (can be overloaded to text | |
608 | */ | |
609 | public $KEY = null; | |
610 | ||
611 | ||
612 | public function __construct($firstname, $lastname, $displayname = null, $sortname = null, $nickname = null) | |
613 | { | |
614 | $this->set('VERSION', '3.0'); | |
615 | $this->setName($firstname, $lastname, $displayname, $sortname, $nickname); | |
616 | } | |
617 | ||
618 | public function &set($name, $value) | |
619 | { | |
620 | $field = new PlVcardField(null, $name, $value); | |
621 | $name = str_replace('-', '_', $name); | |
622 | $this->$name = $field; | |
623 | return $field; | |
624 | } | |
625 | ||
626 | public function &add($name, $value) | |
627 | { | |
628 | $field = new PlVcardField(null, $name, $value); | |
629 | array_push($this->$name, $field); | |
630 | return $field; | |
631 | } | |
632 | ||
633 | public function &addInGroup($group, $name, $value) | |
634 | { | |
635 | $field = new PlVcardField($group, $name, $value); | |
636 | array_push($this->$name, $field); | |
637 | return $field; | |
638 | } | |
639 | ||
640 | public function setName($firstname, $lastname, $displayname = null, $sortname = null, $nickname = null) | |
641 | { | |
642 | $additional = array(); | |
643 | if (is_array($firstname)) { | |
644 | $given = array_shift($firstname); | |
645 | $additional = $firstname; | |
646 | } else { | |
647 | $given = $firstname; | |
648 | } | |
649 | if (is_array($lastname)) { | |
650 | $l = array_shift($lastname); | |
651 | $additional = array_merge($additional, $lastname); | |
652 | $lastname = $l; | |
653 | } | |
654 | if (is_null($displayname)) { | |
655 | $displayname = $given . ' ' . $lastname; | |
656 | } | |
657 | if (is_null($sortname)) { | |
658 | $sortname = $lastname; | |
659 | } | |
660 | $this->set('N', new N_Field($lastname, $given, $additional, null, null)); | |
661 | $this->set('FN', $displayname); | |
662 | if (!is_null($nickname)) { | |
663 | $this->set('NICKNAME', $nickname); | |
664 | } | |
665 | $this->set('SORT-STRING', $sortname); | |
666 | } | |
667 | ||
668 | public function addHome($street, $extra, $postBox, $postCode, $city, | |
669 | $region, $country, $pref = false, $postal = true, | |
670 | $parcel = true) | |
671 | { | |
672 | $group = 'HOME' . count($this->ADR); | |
673 | $field =& $this->addInGroup($group, 'ADR', | |
674 | new ADR_Field($postBox, $extra, $street, $city, | |
675 | $region, $postCode, $country)); | |
676 | $field->TYPE = new PlFlagset(); | |
677 | $field->TYPE->addFlag('home'); | |
678 | $field->TYPE->addFlag('dom'); | |
679 | $field->TYPE->addFlag('intl'); | |
680 | if ($pref) { | |
681 | $field->TYPE->addFlag('pref'); | |
682 | } | |
683 | if ($postal) { | |
684 | $field->TYPE->addFlag('postal'); | |
685 | } | |
686 | if ($parcel) { | |
687 | $field->TYPE->addFlag('parcel'); | |
688 | } | |
689 | return $group; | |
690 | } | |
691 | ||
692 | public function addWork($organisation, $units, $title, $role, | |
693 | $street, $extra, $postBox, $postCode, $city, | |
694 | $region, $country) | |
695 | { | |
696 | $group = 'WORK' . count($this->ORG); | |
697 | $this->addInGroup($group, 'ORG', | |
698 | new ORG_Field($organisation, $units)); | |
699 | if (!is_null($title)) { | |
700 | $this->addInGroup($group, 'TITLE', $title); | |
701 | } | |
702 | if (!is_null($role)) { | |
703 | $this->addInGroup($group, 'ROLE', $role); | |
704 | } | |
705 | $field =& $this->addInGroup($group, 'ADR', | |
706 | new ADR_Field($postBox, $extra, $street, $city, | |
707 | $region, $postCode, $country)); | |
708 | $field->TYPE = new PlFlagset(); | |
709 | $field->TYPE->addFlag('work'); | |
710 | return $group; | |
711 | } | |
712 | ||
713 | public function addTel($group, $tel, $fax = false, $msg = false, $voice = true, | |
714 | $video = false, $cell = false, $pref = false) | |
715 | { | |
716 | $home = is_null($group) || substr($group, 0, 4) == 'HOME'; | |
717 | $work = !$home; | |
718 | ||
719 | $field =& $this->addInGroup($group, 'TEL', $tel); | |
720 | $field->TYPE = new PlFlagset(); | |
721 | foreach (array('home', 'work', 'fax', 'msg', 'voice', 'video', 'cell', 'pref') | |
722 | as $f) { | |
723 | if ($$f) { | |
724 | $field->TYPE->addFlag($f); | |
725 | } | |
726 | } | |
727 | } | |
728 | ||
729 | public function addMail($group, $mail, $pref = false) | |
730 | { | |
731 | $field =& $this->addInGroup($group, 'EMAIL', $mail); | |
732 | $field->TYPE = new PlFlagset(); | |
733 | $field->TYPE->addFlag('internet'); | |
734 | if ($pref) { | |
735 | $field->TYPE->addFlag('pref'); | |
736 | } | |
737 | } | |
738 | ||
739 | public function setPhoto($data, $format = 'JPEG') | |
740 | { | |
741 | $field =& $this->set('PHOTO', $data); | |
742 | $field->TYPE = $format; | |
743 | } | |
744 | ||
745 | public function show() | |
746 | { | |
747 | if (is_null($this->FN) || is_null($this->N) || is_null($this->VERSION)) { | |
748 | trigger_error('Missing mandatoring field in vcard', E_USER_ERROR); | |
749 | return; | |
750 | } | |
751 | PlVcardField::output(null, 'BEGIN', null, 'VCARD'); | |
752 | foreach ($this as $key => $value) { | |
753 | if (is_array($value)) { | |
754 | foreach ($value as $entry) { | |
755 | $entry->show(); | |
756 | } | |
757 | } else if (!is_null($value)) { | |
758 | $value->show(); | |
759 | } | |
760 | } | |
761 | PlVcardField::output(null, 'END', null, 'VCARD'); | |
762 | } | |
763 | } | |
764 | ||
765 | ||
766 | /** Abstract representation of a vcard. | |
767 | * A VCard file can contain several 'physical' vcards. So, this class | |
768 | * handle a vcard as a set of 'PlVCardEntry', each entry describes a | |
769 | * profile. | |
770 | * | |
771 | * To use this tool, you MUST define a new class that inherists this class | |
772 | * and implements fetch() and buildEntry(). Fetch build an iterator that | |
773 | * list a sequence of object (there is no constraint on the type of object). | |
774 | * This objects are given to buildEntry() that MUST use the object to | |
775 | * build a PlVCardEntry object. | |
776 | * | |
777 | * Example: | |
778 | * | |
779 | * <code> | |
780 | * protected function fetch() { | |
781 | * return new PlArrayIterator(array(id1, id2, id3)); | |
782 | * } | |
783 | * | |
784 | * protected function buildEntry($object) { | |
785 | * $profile = fetchProfile($object['value']); | |
786 | * $entry = new PlVCardEntry($profile['firstname'], $profile['name'], ...); | |
787 | * for ($adr in $profile) { | |
788 | * $entry->addHome($street, $ext, $postCode, $city, ...); | |
789 | * } | |
790 | * ... | |
791 | * return $entry; | |
792 | * } | |
793 | * </code> | |
794 | */ | |
795 | abstract class PlVCard | |
796 | { | |
797 | /* VCard parameters */ | |
798 | ||
799 | /** Charset of the text fields | |
800 | */ | |
801 | static public $charset = 'UTF-8'; | |
802 | ||
803 | /** Is line folding activated. | |
804 | * Line folding consists in breaking too long logical lines | |
805 | * into several physical lines. | |
806 | * | |
807 | * RFC2425 and 2426 indicates that folding SHOULD be used | |
808 | * on lines longer than 75 characters, but it seems to fail | |
809 | * on some systems. | |
810 | */ | |
811 | static public $folding = true; | |
812 | ||
813 | /** Do we escape binary (base64) content like text content. | |
814 | * | |
815 | * RFC2426 does not mention escaping on binary values, but this | |
816 | * seems to bee required for some clients. | |
817 | */ | |
818 | static public $escapeBinary = false; | |
819 | ||
820 | /** Build an iterator that will be used to build the entries. | |
821 | */ | |
822 | protected abstract function fetch(); | |
823 | ||
824 | /** Build a entry from an object. | |
825 | */ | |
826 | protected abstract function buildEntry($item); | |
827 | ||
828 | /** Output a VCard | |
829 | */ | |
830 | public function show() | |
831 | { | |
eda18149 FB |
832 | /* XXX: RFC2425 defines the mime content-type text/directory. |
833 | * VCard inherits this type as a profile type. Maybe test/x-vcard | |
834 | * could be better. To be checked. | |
835 | */ | |
a286fc7a | 836 | pl_cached_dynamic_content_headers("text/directory; profile=vCard", "utf-8"); |
eda18149 FB |
837 | |
838 | $it = $this->fetch(); | |
839 | while ($item = $it->next()) { | |
840 | $entry = $this->buildEntry($item); | |
841 | $entry->show(); | |
842 | } | |
843 | exit; | |
844 | } | |
845 | } | |
846 | ||
847 | // vim:set et sw=4 sts=4 sws=4 foldmethod=marker enc=utf-8: | |
848 | ?> |