| 1 | <?php |
| 2 | /*************************************************************************** |
| 3 | * Copyright (C) 2003-2011 Polytechnique.org * |
| 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 | { |
| 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 | */ |
| 836 | pl_cached_dynamic_content_headers("text/directory; profile=vCard", "utf-8"); |
| 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 | ?> |