2 /***************************************************************************
3 * Copyright (C) 2003-2010 Polytechnique.org *
4 * http://opensource.polytechnique.org/ *
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. *
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. *
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 *
19 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA *
20 **************************************************************************/
26 /** Default encoding for the different fields.
28 private static $defaultEncoding = array('BEGIN' => 'limited',
32 'PROFILE' => 'limited',
35 'NICKNAME' => 'text*',
38 'ADR' => 'structured',
40 'TEL' => 'phone-number',
44 'GEO' => 'structured',
49 'ORG' => 'structured',
50 'CATEGORIES' => 'text*',
54 'SORT-STRING' => 'text',
58 'VERSION' => 'limited',
75 /* RFC2425 parameters */
77 /** ENCODING: encoding of the field.
78 * default is 8bit, only 'b' is supported for binary fields
80 public $ENCODING = null
;
82 /** VALUE: type of the value of the field.
83 * available types from RFC2425 are:
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
95 * available types from RFC2426 are:
96 * -binary: (encoding type must be specified)
97 * -vcard: inlined vcard (encoded as text)
102 public $VALUE = null
;
104 /** CHARSET: charset of the value of the field.
106 public $CHARSET = null
;
108 /** LANGUAGE: lanugage of the field.
110 public $LANGUAGE = null
;
112 /** CONTEXT: context of the value.
114 public $CONTEXT = null
;
117 /* RFC2426 parameters */
119 /** TYPE: variants of the type.
124 public function __construct($group, $name, $value)
126 $this->group
= $group;
128 $this->value
= $value;
130 $type = @self
::$defaultEncoding[$name];
131 if (is_null($type)) {
134 if ($type == 'binary') {
135 $this->ENCODING
= 'b';
136 } else if ($type == 'text' ||
$type == 'text*' ||
$type == 'structured') {
137 $this->CHARSET
= PlVCard
::$charset;
141 public function show()
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)) {
153 $encoding = $this->VALUE
;
154 if (is_null($encoding)) {
155 $encoding = @self
::$defaultEncoding[$this->name
];
157 if (is_null($encoding)) {
158 // let say default encoding is 'text'
161 self
::output($this->group
, $this->name
, $params, self
::format($this->value
, $encoding));
164 static public function format($value, $format)
166 if (substr($format, -1) == '*') {
167 $format = substr($format, 0, -1);
168 if (is_array($value)) {
170 foreach ($value as $v) {
171 $vals[] = self
::format($v, $format);
173 return implode(',', $vals);
176 if (is_null($value)) {
181 return str_replace(',', '.', $value);
184 if ($value == 'TRUE' ||
$value == 'FALSE') {
187 return $value ?
'TRUE' : 'FALSE';
190 if (!PlVCard
::$escapeBinary) {
191 return base64_encode($value);
193 $value = base64_encode($value);
198 if (PlVCard
::$charset != 'UTF-8' && $format != 'binary') {
199 $value = iconv('UTF-8', PlVCard
::$charset, $value);
201 return str_replace(array('\\', ',', "\r\n", "\r", "\n"),
202 array('\\\\', '\\,', '\\n', '\\n', '\\n'),
207 foreach ($value as $k => $v) {
211 $enc = isset($value->_encoding
[$k]) ?
$value->_encoding
[$k] : $value->_encoding
['@@EXTRA@@'];
212 $vals[] = str_replace(';', '\\;', self
::format($v, $enc));
214 return implode(';', $vals);
225 static public function output($group, $name, $params, $value)
228 if (!is_null($group)) {
229 $str .= $group . '.';
232 if (!is_null($params)) {
233 foreach ($params as $pn => $pv) {
234 $str .= ';' . $pn . '=' . $pv;
237 $str .= ':' . $value;
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);
251 /** Structure of the N type as described in RFC2426.
255 public $_encoding = array('familyName' => 'text*',
256 'givenName' => 'text*',
257 'additionalName' => 'text*',
258 'honorificPrefixes' => 'text*',
259 'honorificSuffixes' => 'text*');
264 public $familyName = null
;
269 public $givenName = null
;
271 /** The additional names
274 public $additionalName = null
;
276 /** Honorific prefixes
279 public $honorificPrefixes = null
;
281 /** Honorific suffixes
284 public $honorificSuffixes = null
;
286 public function __construct($family, $given, $additional, $prefix, $suffix)
288 $this->familyName
= $family;
289 $this->givenName
= $given;
290 $this->additionalName
= $additional;
292 $this->honorificPrefixes
= $prefix;
293 $this->honorificSuffixes
= $suffix;
298 /** Structure of the ADR type as described in RFC2426.
302 public $_encoding = array('postOfficeBox' => 'text',
303 'extendedAddress' => 'text*',
304 'streetAddress' => 'text*',
305 'locality' => 'text',
307 'postalCode' => 'text',
308 'countryName' => 'text');
310 /** The post office box
313 public $postOfficeBox = null
;
315 /** Extended address.
318 public $extendedAddress = null
;
323 public $streetAddress = null
;
328 public $locality = null
;
333 public $region = null
;
338 public $postalCode = null
;
343 public $countryName = null
;
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;
358 /** Structure of the ORG type as described in RFC2426.
362 public $_encoding = array('name' => 'text',
363 '@@EXTRA@@' => 'text');
366 /** Organisation name.
372 * -type: several entries
374 * Use dynamic PHP members to distinguish
375 * multi-fields from text-list.
378 public function __construct($org, $units)
381 if (!is_null($units)) {
382 if (is_array($units)) {
383 foreach ($units as $k => $v) {
388 $this->unit_0
= $units;
394 /** Structure of the GEO type as described in RFC2426.
398 public $_encoding = array('latitude' => 'float',
399 'longitude' => 'float');
412 public function __construct($lat, $lon)
414 $this->latitude
= $lat;
415 $this->longitude
= $lon;
423 /** SOURCE: source of the vCard.
427 public $SOURCE = null
;
429 /** NAME: name of the entry.
435 /** PROFILE: profile type.
436 * -type: a registered profile name (vCard)
439 public $PROFILE = null
;
443 /* Identification fields */
445 /** FN: Formatted name.
451 /** N: Name structure.
457 /** NICKNAME: List of nick names.
460 public $NICKNAME = null
;
462 /** PHOTO: Photo of the object identified by the vcard.
463 * -type: binary, can be reset to URL
465 public $PHOTO = null
;
468 * -type: date, can be reset to date-time
473 /* Delivery addressing */
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)
479 public $ADR = array();
481 /** LABEL: formatted text representing a delivery address.
483 * -variant flags: dom, intl, postal, parcel, home, work, pref (default: intl,postal,parcel,work)
485 public $LABEL = array();
488 /* Telecommunication addressing */
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)
494 public $TEL = array();
496 /** EMAIL: electroning mail address.
498 * -variant flags: internet, x400, pref (default: internet)
500 public $EMAIL = array();
502 /** MAILER: type of mailer used...
505 public $MAILER = array();
511 * -type: utc-offset (can be reset to a text value)
515 /** GEO: Geographical coordinates.
516 * -type: geo structure
523 /** TITLE: job title, functional position or function.
526 public $TITLE = array();
528 /** ROLE: role, occupation, business category.
531 public $ROLE = array();
533 /** LOGO: logo of the organization.
534 * -type: binary (can be reset to uri)
536 public $LOGO = array();
538 /** AGENT: define information about another person.
539 * -type: vcard (can be reset to a uri)
541 public $AGENT = array();
543 /** ORG: Organizational name and units.
544 * -type: org structure
546 public $ORG = array();
551 /** CATEGORIES: list of categories.
554 public $CATEGORIES = null
;
556 /** NOTE: supplemental information or comment.
561 /** PRODID: Identifier of the product that created the card.
562 * -type: text (ISO 9070)
564 public $PRODID = null
;
566 /** REV: revision information about the card.
567 * -type: date-time (can be reset to a simple date)
571 /** SORT-STRING: informations on how to sort this card
574 public $SORT_STRING = null
;
576 /** SOUND: digital sound content that annotates the card.
577 * -type: binary (can be reset to a uri)
579 public $SOUND = null
;
581 /** UID: globaly unique identifier corresponding to the object.
583 * -variant: IANA standard format identifier (optionnal)
587 /** URL: url describing the object the vcard refers to.
592 /** VERSION: format version of the vcard
596 public $VERSION = null
;
601 /** CLASS: access classification.
602 * -type: text (eg.: PUBLIC, PRIVATE, CONFIDENTIAL...)
604 public $CLASS = null
;
606 /** KEY: public key or authentication certificate associated with the object.
607 * -type: binary (can be overloaded to text
612 public function __construct($firstname, $lastname, $displayname = null
, $sortname = null
, $nickname = null
)
614 $this->set('VERSION', '3.0');
615 $this->setName($firstname, $lastname, $displayname, $sortname, $nickname);
618 public function &set($name, $value)
620 $field = new PlVcardField(null
, $name, $value);
621 $name = str_replace('-', '_', $name);
622 $this->$name = $field;
626 public function &add($name, $value)
628 $field = new PlVcardField(null
, $name, $value);
629 array_push($this->$name, $field);
633 public function &addInGroup($group, $name, $value)
635 $field = new PlVcardField($group, $name, $value);
636 array_push($this->$name, $field);
640 public function setName($firstname, $lastname, $displayname = null
, $sortname = null
, $nickname = null
)
642 $additional = array();
643 if (is_array($firstname)) {
644 $given = array_shift($firstname);
645 $additional = $firstname;
649 if (is_array($lastname)) {
650 $l = array_shift($lastname);
651 $additional = array_merge($additional, $lastname);
654 if (is_null($displayname)) {
655 $displayname = $given . ' ' . $lastname;
657 if (is_null($sortname)) {
658 $sortname = $lastname;
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);
665 $this->set('SORT-STRING', $sortname);
668 public function addHome($street, $extra, $postBox, $postCode, $city,
669 $region, $country, $pref = false
, $postal = true
,
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');
681 $field->TYPE
->addFlag('pref');
684 $field->TYPE
->addFlag('postal');
687 $field->TYPE
->addFlag('parcel');
692 public function addWork($organisation, $units, $title, $role,
693 $street, $extra, $postBox, $postCode, $city,
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);
702 if (!is_null($role)) {
703 $this->addInGroup($group, 'ROLE', $role);
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');
713 public function addTel($group, $tel, $fax = false
, $msg = false
, $voice = true
,
714 $video = false
, $cell = false
, $pref = false
)
716 $home = is_null($group) ||
substr($group, 0, 4) == 'HOME';
719 $field =& $this->addInGroup($group, 'TEL', $tel);
720 $field->TYPE
= new PlFlagset();
721 foreach (array('home', 'work', 'fax', 'msg', 'voice', 'video', 'cell', 'pref')
724 $field->TYPE
->addFlag($f);
729 public function addMail($group, $mail, $pref = false
)
731 $field =& $this->addInGroup($group, 'EMAIL', $mail);
732 $field->TYPE
= new PlFlagset();
733 $field->TYPE
->addFlag('internet');
735 $field->TYPE
->addFlag('pref');
739 public function setPhoto($data, $format = 'JPEG')
741 $field =& $this->set('PHOTO', $data);
742 $field->TYPE
= $format;
745 public function show()
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
);
751 PlVcardField
::output(null
, 'BEGIN', null
, 'VCARD');
752 foreach ($this as $key => $value) {
753 if (is_array($value)) {
754 foreach ($value as $entry) {
757 } else if (!is_null($value)) {
761 PlVcardField
::output(null
, 'END', null
, 'VCARD');
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
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.
780 * protected function fetch() {
781 * return new PlArrayIterator(array(id1, id2, id3));
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, ...);
795 abstract class PlVCard
797 /* VCard parameters */
799 /** Charset of the text fields
801 static public $charset = 'UTF-8';
803 /** Is line folding activated.
804 * Line folding consists in breaking too long logical lines
805 * into several physical lines.
807 * RFC2425 and 2426 indicates that folding SHOULD be used
808 * on lines longer than 75 characters, but it seems to fail
811 static public $folding = true
;
813 /** Do we escape binary (base64) content like text content.
815 * RFC2426 does not mention escaping on binary values, but this
816 * seems to bee required for some clients.
818 static public $escapeBinary = false
;
820 /** Build an iterator that will be used to build the entries.
822 protected abstract function fetch();
824 /** Build a entry from an object.
826 protected abstract function buildEntry($item);
830 public function show()
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.
836 pl_cached_dynamic_content_headers("text/directory; profile=vCard", "utf-8");
838 $it = $this->fetch();
839 while ($item = $it->next()) {
840 $entry = $this->buildEntry($item);
847 // vim:set et sw=4 sts=4 sws=4 foldmethod=marker enc=utf-8: