Add a guard against overlapping transactions.
[platal.git] / classes / plvcard.php
CommitLineData
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 */
24class 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 */
253class 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 */
300class 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 */
360class 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 */
396class 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
419class 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 */
795abstract 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?>