Overview

Packages

  • Horde
    • Data
  • None
  • Turba

Classes

  • Turba
  • Turba_Api
  • Turba_Driver
  • Turba_Driver_Facebook
  • Turba_Driver_Favourites
  • Turba_Driver_Group
  • Turba_Driver_Imsp
  • Turba_Driver_Kolab
  • Turba_Driver_Ldap
  • Turba_Driver_Prefs
  • Turba_Driver_Share
  • Turba_Driver_Sql
  • Turba_Driver_Vbook
  • Turba_Exception
  • Turba_Factory_Driver
  • Turba_Form_AddContact
  • Turba_Form_Contact
  • Turba_Form_ContactBase
  • Turba_Form_CreateAddressBook
  • Turba_Form_DeleteAddressBook
  • Turba_Form_EditAddressBook
  • Turba_Form_EditContact
  • Turba_Form_EditContactGroup
  • Turba_List
  • Turba_LoginTasks_SystemTask_Upgrade
  • Turba_Object
  • Turba_Object_Group
  • Turba_Test
  • Turba_View_Browse
  • Turba_View_Contact
  • Turba_View_DeleteContact
  • Turba_View_Duplicates
  • Turba_View_EditContact
  • Turba_View_List
  • Overview
  • Package
  • Class
  • Tree
   1: <?php
   2: /**
   3:  * The Turba_Driver:: class provides a common abstracted interface to the
   4:  * various directory search drivers.  It includes functions for searching,
   5:  * adding, removing, and modifying directory entries.
   6:  *
   7:  * Copyright 2000-2012 Horde LLC (http://www.horde.org/)
   8:  *
   9:  * See the enclosed file LICENSE for license information (ASL).  If you
  10:  * did not receive this file, see http://www.horde.org/licenses/apache.
  11:  *
  12:  * @author   Chuck Hagenbuch <chuck@horde.org>
  13:  * @author   Jon Parise <jon@csh.rit.edu>
  14:  * @category Horde
  15:  * @license  http://www.horde.org/licenses/apache ASL
  16:  * @package  Turba
  17:  */
  18: class Turba_Driver implements Countable
  19: {
  20:     /**
  21:      * The symbolic title of this source.
  22:      *
  23:      * @var string
  24:      */
  25:     public $title;
  26: 
  27:     /**
  28:      * Hash describing the mapping between Turba attributes and
  29:      * driver-specific fields.
  30:      *
  31:      * @var array
  32:      */
  33:     public $map = array();
  34: 
  35:     /**
  36:      * Hash with all tabs and their fields.
  37:      *
  38:      * @var array
  39:      */
  40:     public $tabs = array();
  41: 
  42:     /**
  43:      * List of all fields that can be accessed in the backend (excludes
  44:      * composite attributes, etc.).
  45:      *
  46:      * @var array
  47:      */
  48:     public $fields = array();
  49: 
  50:     /**
  51:      * Array of fields that must match exactly.
  52:      *
  53:      * @var array
  54:      */
  55:     public $strict = array();
  56: 
  57:     /**
  58:      * Array of fields to search "approximately" (@see
  59:      * config/backends.php).
  60:      *
  61:      * @var array
  62:      */
  63:     public $approximate = array();
  64: 
  65:     /**
  66:      * The name of a field to store contact list names in if not the default.
  67:      *
  68:      * @var string
  69:      */
  70:     public $listNameField = null;
  71: 
  72:     /**
  73:      * The name of a field to use as an alternative to the name field if that
  74:      * one is empty.
  75:      *
  76:      * @var string
  77:      */
  78:     public $alternativeName = null;
  79: 
  80:     /**
  81:      * The internal name of this source.
  82:      *
  83:      * @var string
  84:      */
  85:     protected $_name;
  86: 
  87:     /**
  88:      * Hash holding the driver's additional parameters.
  89:      *
  90:      * @var array
  91:      */
  92:     protected $_params = array();
  93: 
  94:     /**
  95:      * What can this backend do?
  96:      *
  97:      * @var array
  98:      */
  99:     protected $_capabilities = array();
 100: 
 101:     /**
 102:      * Number of contacts in this source.
 103:      *
 104:      * @var integer
 105:      */
 106:     protected $_count = null;
 107: 
 108:     /**
 109:      * Hold the value for the owner of this address book.
 110:      *
 111:      * @var string
 112:      */
 113:     protected $_contact_owner = '';
 114: 
 115:     /**
 116:      * Constructs a new Turba_Driver object.
 117:      *
 118:      * @param string $name   Source name
 119:      * @param array $params  Hash containing additional configuration
 120:      *                       parameters.
 121:      */
 122:     public function __construct($name = '', array $params = array())
 123:     {
 124:         $this->_name = $name;
 125:         $this->_params = $params;
 126:     }
 127: 
 128:     /**
 129:      * Returns the current driver's additional parameters.
 130:      *
 131:      * @return array  Hash containing the driver's additional parameters.
 132:      */
 133:     public function getParams()
 134:     {
 135:         return $this->_params;
 136:     }
 137: 
 138:     /**
 139:      * Checks if this backend has a certain capability.
 140:      *
 141:      * @param string $capability  The capability to check for.
 142:      *
 143:      * @return boolean  Supported or not.
 144:      */
 145:     public function hasCapability($capability)
 146:     {
 147:         return !empty($this->_capabilities[$capability]);
 148:     }
 149: 
 150:     /**
 151:      * Returns the attributes that are blob types.
 152:      *
 153:      * @return array  List of blob attributes in the array keys.
 154:      */
 155:     public function getBlobs()
 156:     {
 157:         global $attributes;
 158: 
 159:         $blobs = array();
 160:         foreach (array_keys($this->fields) as $attribute) {
 161:             if (isset($attributes[$attribute]) &&
 162:                 $attributes[$attribute]['type'] == 'image') {
 163:                 $blobs[$attribute] = true;
 164:             }
 165:         }
 166: 
 167:         return $blobs;
 168:     }
 169: 
 170:     /**
 171:      * Translates the keys of the first hash from the generalized Turba
 172:      * attributes to the driver-specific fields. The translation is based on
 173:      * the contents of $this->map.
 174:      *
 175:      * @param array $hash  Hash using Turba keys.
 176:      *
 177:      * @return array  Translated version of $hash.
 178:      */
 179:     public function toDriverKeys(array $hash)
 180:     {
 181:         /* Handle category. */
 182:         if (!empty($hash['category'])) {
 183:             if (is_array($hash['category']) && !empty($hash['category']['new'])) {
 184:                 $cManager = new Horde_Prefs_CategoryManager();
 185:                 $cManager->add($hash['category']['value']);
 186:                 $hash['category'] = $hash['category']['value'];
 187:             } elseif (is_array($hash['category'])) {
 188:                 $hash['category'] = $hash['category']['value'];
 189:             }
 190:         }
 191: 
 192:         // Add composite fields to $hash if at least one field part exists
 193:         // and the composite field will be saved to storage.
 194:         // Otherwise composite fields won't be computed during an import.
 195:         foreach ($this->map as $key => $val) {
 196:             if (!is_array($val) ||
 197:                 empty($this->map[$key]['attribute']) ||
 198:                 array_key_exists($key, $hash)) {
 199:                 continue;
 200:             }
 201: 
 202:             foreach ($this->map[$key]['fields'] as $mapfields) {
 203:                 if (isset($hash[$mapfields])) {
 204:                     // Add composite field
 205:                     $hash[$key] = null;
 206:                     break;
 207:                 }
 208:             }
 209:         }
 210: 
 211:         if (!empty($hash['name']) &&
 212:             !empty($this->listNameField) &&
 213:             !empty($hash['__type']) &&
 214:             is_array($this->map['name']) &&
 215:             ($hash['__type'] == 'Group')) {
 216:             $hash[$this->listNameField] = $hash['name'];
 217:             unset($hash['name']);
 218:         }
 219: 
 220:         $fields = array();
 221:         foreach ($hash as $key => $val) {
 222:             if (isset($this->map[$key])) {
 223:                 if (!is_array($this->map[$key])) {
 224:                     $fields[$this->map[$key]] = $val;
 225:                 } elseif (!empty($this->map[$key]['attribute'])) {
 226:                     $fieldarray = array();
 227:                     foreach ($this->map[$key]['fields'] as $mapfields) {
 228:                         $fieldarray[] = isset($hash[$mapfields])
 229:                             ? $hash[$mapfields]
 230:                             : '';
 231:                     }
 232:                     $fields[$this->map[$key]['attribute']] = Turba::formatCompositeField($this->map[$key]['format'], $fieldarray);
 233:                 } else {
 234:                     // If 'parse' is not specified, use 'format' and 'fields'.
 235:                     if (!isset($this->map[$key]['parse'])) {
 236:                         $this->map[$key]['parse'] = array(
 237:                             array(
 238:                                 'format' => $this->map[$key]['format'],
 239:                                 'fields' => $this->map[$key]['fields']
 240:                             )
 241:                         );
 242:                     }
 243:                     foreach ($this->map[$key]['parse'] as $parse) {
 244:                         $splitval = sscanf($val, $parse['format']);
 245:                         $count = 0;
 246:                         $tmp_fields = array();
 247:                         foreach ($parse['fields'] as $mapfield) {
 248:                             if (isset($hash[$mapfield])) {
 249:                                 // If the compositing fields are set
 250:                                 // individually, then don't set them at all.
 251:                                 break 2;
 252:                             }
 253:                             $tmp_fields[$this->map[$mapfield]] = $splitval[$count++];
 254:                         }
 255:                         // Exit if we found the best match.
 256:                         if ($splitval[$count - 1] !== null) {
 257:                             break;
 258:                         }
 259:                     }
 260:                     $fields = array_merge($fields, $tmp_fields);
 261:                 }
 262:             }
 263:         }
 264: 
 265:         return $fields;
 266:     }
 267: 
 268:     /**
 269:      * Takes a hash of Turba key => search value and return a (possibly
 270:      * nested) array, using backend attribute names, that can be turned into a
 271:      * search by the driver. The translation is based on the contents of
 272:      * $this->map, and includes nested OR searches for composite fields.
 273:      *
 274:      * @param array  $criteria      Hash of criteria using Turba keys.
 275:      * @param string $search_type   OR search or AND search?
 276:      * @param array  $strict        Fields that must be matched exactly.
 277:      * @param boolean $match_begin  Whether to match only at beginning of
 278:      *                              words.
 279:      *
 280:      * @return array  An array of search criteria.
 281:      */
 282:     public function makeSearch($criteria, $search_type, array $strict,
 283:                                $match_begin = false)
 284:     {
 285:         $search = $search_terms = $subsearch = $strict_search = array();
 286:         $glue = $temp = '';
 287:         $lastChar = '\"';
 288:         $blobs = $this->getBlobs();
 289: 
 290:         foreach ($criteria as $key => $val) {
 291:             if (!isset($this->map[$key])) {
 292:                 continue;
 293:             }
 294:             if (is_array($this->map[$key])) {
 295:                 /* Composite field, break out the search terms. */
 296:                 $parts = explode(' ', $val);
 297:                 if (count($parts) > 1) {
 298:                     /* Only parse if there was more than 1 search term and
 299:                      * 'AND' the cumulative subsearches. */
 300:                     for ($i = 0; $i < count($parts); ++$i) {
 301:                         $term = $parts[$i];
 302:                         $firstChar = substr($term, 0, 1);
 303:                         if ($firstChar == '"') {
 304:                             $temp = substr($term, 1, strlen($term) - 1);
 305:                             $done = false;
 306:                             while (!$done && $i < count($parts) - 1) {
 307:                                 $lastChar = substr($parts[$i + 1], -1);
 308:                                 if ($lastChar == '"') {
 309:                                     $temp .= ' ' . substr($parts[$i + 1], 0, -1);
 310:                                     $done = true;
 311:                                 } else {
 312:                                     $temp .= ' ' . $parts[$i + 1];
 313:                                 }
 314:                                 ++$i;
 315:                             }
 316:                             $search_terms[] = $temp;
 317:                         } else {
 318:                             $search_terms[] = $term;
 319:                         }
 320:                     }
 321:                     $glue = 'AND';
 322:                 } else {
 323:                     /* If only one search term, use original input and
 324:                        'OR' the searces since we're only looking for 1
 325:                        term in any of the composite fields. */
 326:                     $search_terms[0] = $val;
 327:                     $glue = 'OR';
 328:                 }
 329: 
 330:                 foreach ($this->map[$key]['fields'] as $field) {
 331:                     if (!empty($blobs[$field])) {
 332:                         continue;
 333:                     }
 334:                     $field = $this->toDriver($field);
 335:                     if (!empty($strict[$field])) {
 336:                         /* For strict matches, use the original search
 337:                          * vals. */
 338:                         $strict_search[] = array(
 339:                             'field' => $field,
 340:                             'op' => '=',
 341:                             'test' => $val,
 342:                         );
 343:                     } else {
 344:                         /* Create a subsearch for each individual search
 345:                          * term. */
 346:                         if (count($search_terms) > 1) {
 347:                             /* Build the 'OR' search for each search term
 348:                              * on this field. */
 349:                             $atomsearch = array();
 350:                             for ($i = 0; $i < count($search_terms); ++$i) {
 351:                                 $atomsearch[] = array(
 352:                                     'field' => $field,
 353:                                     'op' => 'LIKE',
 354:                                     'test' => $search_terms[$i],
 355:                                     'begin' => $match_begin,
 356:                                     'approximate' => !empty($this->approximate[$field]),
 357:                                 );
 358:                             }
 359:                             $atomsearch[] = array(
 360:                                 'field' => $field,
 361:                                 'op' => '=',
 362:                                 'test' => '',
 363:                                 'begin' => $match_begin,
 364:                                 'approximate' => !empty($this->approximate[$field])
 365:                             );
 366: 
 367:                             $subsearch[] = array('OR' => $atomsearch);
 368:                             unset($atomsearch);
 369:                             $glue = 'AND';
 370:                         } else {
 371:                             /* $parts may have more than one element, but
 372:                              * if they are all quoted we will only have 1
 373:                              * $subsearch. */
 374:                             $subsearch[] = array(
 375:                                 'field' => $field,
 376:                                 'op' => 'LIKE',
 377:                                 'test' => $search_terms[0],
 378:                                 'begin' => $match_begin,
 379:                                 'approximate' => !empty($this->approximate[$field]),
 380:                             );
 381:                             $glue = 'OR';
 382:                         }
 383:                     }
 384:                 }
 385:                 if (count($subsearch)) {
 386:                     $search[] = array($glue => $subsearch);
 387:                 }
 388:             } else {
 389:                 /* Not a composite field. */
 390:                 if (!empty($blobs[$key])) {
 391:                     continue;
 392:                 }
 393:                 if (!empty($strict[$this->map[$key]])) {
 394:                     $strict_search[] = array(
 395:                         'field' => $this->map[$key],
 396:                         'op' => '=',
 397:                         'test' => $val,
 398:                     );
 399:                 } else {
 400:                     $search[] = array(
 401:                         'field' => $this->map[$key],
 402:                         'op' => 'LIKE',
 403:                         'test' => $val,
 404:                         'begin' => $match_begin,
 405:                         'approximate' => !empty($this->approximate[$this->map[$key]]),
 406:                     );
 407:                 }
 408:             }
 409:         }
 410: 
 411:         if (count($strict_search) && count($search)) {
 412:             return array(
 413:                 'AND' => array(
 414:                     $search_type => $strict_search,
 415:                     array(
 416:                         $search_type => $search
 417:                     )
 418:                 )
 419:             );
 420:         } elseif (count($strict_search)) {
 421:             return array(
 422:                 'AND' => $strict_search
 423:             );
 424:         } elseif (count($search)) {
 425:             return array(
 426:                 $search_type => $search
 427:             );
 428:         }
 429: 
 430:         return array();
 431:     }
 432: 
 433:     /**
 434:      * Translates a single Turba attribute to the driver-specific
 435:      * counterpart. The translation is based on the contents of
 436:      * $this->map. This ignores composite fields.
 437:      *
 438:      * @param string $attribute  The Turba attribute to translate.
 439:      *
 440:      * @return string  The driver name for this attribute.
 441:      */
 442:     public function toDriver($attribute)
 443:     {
 444:         if (!isset($this->map[$attribute])) {
 445:             return null;
 446:         }
 447: 
 448:         return is_array($this->map[$attribute])
 449:             ? $this->map[$attribute]['fields']
 450:             : $this->map[$attribute];
 451:     }
 452: 
 453:     /**
 454:      * Translates a hash from being keyed on driver-specific fields to being
 455:      * keyed on the generalized Turba attributes. The translation is based on
 456:      * the contents of $this->map.
 457:      *
 458:      * @param array $entry  A hash using driver-specific keys.
 459:      *
 460:      * @return array  Translated version of $entry.
 461:      */
 462:     public function toTurbaKeys(array $entry)
 463:     {
 464:         $new_entry = array();
 465:         foreach ($this->map as $key => $val) {
 466:             if (!is_array($val)) {
 467:                 $new_entry[$key] = (isset($entry[$val]) && strlen($entry[$val]))
 468:                     ? trim($entry[$val])
 469:                     : null;
 470:             }
 471:         }
 472: 
 473:         return $new_entry;
 474:     }
 475: 
 476:     /**
 477:      * Searches the source based on the provided criteria.
 478:      *
 479:      * @todo Allow $criteria to contain the comparison operator (<, =, >,
 480:      *       'like') and modify the drivers accordingly.
 481:      *
 482:      * @param array $search_criteria  Hash containing the search criteria.
 483:      * @param string $sort_order      The requested sort order which is passed
 484:      *                                to Turba_List::sort().
 485:      * @param string $search_type     Do an AND or an OR search (defaults to
 486:      *                                AND).
 487:      * @param array $return_fields    A list of fields to return; defaults to
 488:      *                                all fields.
 489:      * @param array $custom_strict    A list of fields that must match exactly.
 490:      * @param boolean $match_begin    Whether to match only at beginning of
 491:      *                                words.
 492:      * @param boolean $count_only   Only return the count of matching entries,
 493:      *                              not the entries themselves.
 494:      *
 495:      * @return mixed Turba_List|integer  The sorted, filtered list of search
 496:      *                                   results or the number of matching
 497:      *                                   entries (if $count_only is true).
 498:      * @throws Turba_Exception
 499:      */
 500:     public function search(array $search_criteria, $sort_order = null,
 501:                            $search_type = 'AND', array $return_fields = array(),
 502:                            array $custom_strict = array(), $match_begin = false,
 503:                            $count_only = false)
 504:     {
 505:         /* If we are not using Horde_Share, enforce the requirement that the
 506:          * current user must be the owner of the addressbook. */
 507:         $search_criteria['__owner'] = $this->getContactOwner();
 508:         $strict_fields = array($this->toDriver('__owner') => true);
 509: 
 510:         /* Add any fields that must match exactly for this source to the
 511:          * $strict_fields array. */
 512:         foreach ($this->strict as $strict_field) {
 513:             $strict_fields[$strict_field] = true;
 514:         }
 515:         foreach ($custom_strict as $strict_field) {
 516:             $strict_fields[$this->map[$strict_field]] = true;
 517:         }
 518: 
 519:         /* Translate the Turba attributes to driver-specific attributes. */
 520:         $fields = $this->makeSearch($search_criteria, $search_type,
 521:                                     $strict_fields, $match_begin);
 522: 
 523:         if (count($return_fields)) {
 524:             $return_fields_pre = array_unique(array_merge(array('__key', '__type', '__owner', '__members', 'name'), $return_fields));
 525:             $return_fields = array();
 526:             foreach ($return_fields_pre as $field) {
 527:                 $result = $this->toDriver($field);
 528:                 if (is_array($result)) {
 529:                     foreach ($result as $composite_field) {
 530:                         $composite_result = $this->toDriver($composite_field);
 531:                         if ($composite_result) {
 532:                             $return_fields[] = $composite_result;
 533:                         }
 534:                     }
 535:                 } elseif ($result) {
 536:                     $return_fields[] = $result;
 537:                 }
 538:             }
 539:         } else {
 540:             /* Need to force the array to be re-keyed for the (fringe) case
 541:              * where we might have 1 DB field mapped to 2 or more Turba
 542:              * fields */
 543:             $return_fields = array_values(
 544:                 array_unique(array_values($this->fields)));
 545:         }
 546: 
 547:         /* Retrieve the search results from the driver. */
 548:         $objects = $this->_search($fields, $return_fields, $this->toDriverKeys($this->getBlobs()), $count_only);
 549:         if ($count_only) {
 550:             return $objects;
 551:         }
 552:         return $this->_toTurbaObjects($objects, $sort_order);
 553:     }
 554: 
 555:     /**
 556:      * Searches the current address book for duplicate entries.
 557:      *
 558:      * Duplicates are determined by comparing email and name or last name and
 559:      * first name values.
 560:      *
 561:      * @return array  A hash with the following format:
 562:      * <code>
 563:      * array('name' => array('John Doe' => Turba_List, ...), ...)
 564:      * </code>
 565:      * @throws Turba_Exception
 566:      */
 567:     public function searchDuplicates()
 568:     {
 569:         return array();
 570:     }
 571: 
 572:     /**
 573:      * Takes an array of object hashes and returns a Turba_List
 574:      * containing the correct Turba_Objects
 575:      *
 576:      * @param array $objects     An array of object hashes (keyed to backend).
 577:      * @param array $sort_order  Array of hashes describing sort fields.  Each
 578:      *                           hash has the following fields:
 579:      * <pre>
 580:      * ascending - (boolean) Indicating sort direction.
 581:      * field - (string) Sort field.
 582:      * </pre>
 583:      *
 584:      * @return Turba_List  A list object.
 585:      */
 586:     protected function _toTurbaObjects(array $objects, array $sort_order = null)
 587:     {
 588:         $list = new Turba_List();
 589: 
 590:         foreach ($objects as $object) {
 591:             /* Translate the driver-specific fields in the result back to the
 592:              * more generalized common Turba attributes using the map. */
 593:             $object = $this->toTurbaKeys($object);
 594: 
 595:             $done = false;
 596:             if (!empty($object['__type']) &&
 597:                 ucwords($object['__type']) != 'Object') {
 598:                 $class = 'Turba_Object_' . ucwords($object['__type']);
 599:                 if (class_exists($class)) {
 600:                     $list->insert(new $class($this, $object));
 601:                     $done = true;
 602:                 }
 603:             }
 604:             if (!$done) {
 605:                 $list->insert(new Turba_Object($this, $object));
 606:             }
 607:         }
 608: 
 609:         $list->sort($sort_order);
 610: 
 611:         /* Return the filtered (sorted) results. */
 612:         return $list;
 613:     }
 614: 
 615:     /**
 616:      * Returns a list of birthday or anniversary hashes from this source for a
 617:      * certain period.
 618:      *
 619:      * @param Horde_Date $start  The start date of the valid period.
 620:      * @param Horde_Date $end    The end date of the valid period.
 621:      * @param string $category   The timeObjects category to return.
 622:      *
 623:      * @return array  A list of timeObject hashes.
 624:      * @throws Turba Exception
 625:      */
 626:     public function listTimeObjects(Horde_Date $start, Horde_Date $end, $category)
 627:     {
 628:         try {
 629:             $res = $this->getTimeObjectTurbaList($start, $end, $category);
 630:         } catch (Turba_Exception $e) {
 631:             /* Try the default implementation before returning an error */
 632:             $res = $this->_getTimeObjectTurbaListFallback($start, $end, $category);
 633:         }
 634: 
 635:         $t_objects = array();
 636:         while ($ob = $res->next()) {
 637:             $t_object = $ob->getValue($category);
 638:             if (empty($t_object) ||
 639:                 $t_object == '0000-00-00' ||
 640:                 !preg_match('/(\d{4})-(\d{2})-(\d{2})/', $t_object, $match)) {
 641:                 continue;
 642:             }
 643: 
 644:             $t_object = new Horde_Date(array(
 645:                 'mday' => $match[3],
 646:                 'month' => $match[2],
 647:                 'year' => $match[1]
 648:             ));
 649:             if ($t_object->compareDate($end) > 0) {
 650:                 continue;
 651:             }
 652: 
 653:             $t_object_end = new Horde_Date($t_object);
 654:             ++$t_object_end->mday;
 655:             $key = $ob->getValue('__key');
 656: 
 657:             // Calculate the age of the time object
 658:             if ($start->year == $end->year ||
 659:                 $end->year == 9999) {
 660:                 $age = $start->year - $t_object->year;
 661:             } elseif ($t_object->month <= $end->month) {
 662:                 // t_object must be in later year
 663:                 $age = $end->year - $t_object->year;
 664:             } else {
 665:                 // t_object must be in earlier year
 666:                 $age = $start->year - $t_object->year;
 667:             }
 668: 
 669:             $title = sprintf(_("%d. %s of %s"),
 670:                              $age,
 671:                              $GLOBALS['attributes'][$category]['label'],
 672:                              $ob->getValue('name'));
 673: 
 674:             $t_objects[] = array(
 675:                 'id' => $key,
 676:                 'title' => $title,
 677:                 'start' => sprintf('%d-%02d-%02dT00:00:00',
 678:                                    $t_object->year,
 679:                                    $t_object->month,
 680:                                    $t_object->mday),
 681:                 'end' => sprintf('%d-%02d-%02dT00:00:00',
 682:                                  $t_object_end->year,
 683:                                  $t_object_end->month,
 684:                                  $t_object_end->mday),
 685:                 'category' => $ob->getValue('category'),
 686:                 'recurrence' => array('type' => Horde_Date_Recurrence::RECUR_YEARLY_DATE,
 687:                                       'interval' => 1),
 688:                 'params' => array('source' => $this->_name, 'key' => $key),
 689:                 'link' => Horde::url('contact.php', true)->add(array('source' => $this->_name, 'key' => $key))->setRaw(true));
 690:         }
 691: 
 692:         return $t_objects;
 693:     }
 694: 
 695:     /**
 696:      * Default implementation for obtaining a Turba_List to get TimeObjects
 697:      * out of.
 698:      *
 699:      * @param Horde_Date $start  The starting date.
 700:      * @param Horde_Date $end    The ending date.
 701:      * @param string $field      The address book field containing the
 702:      *                           timeObject information (birthday,
 703:      *                           anniversary).
 704:      *
 705:      * @return Turba_List  A list of objects.
 706:      * @throws Turba_Exception
 707:      */
 708:     public function getTimeObjectTurbaList(Horde_Date $start, Horde_Date $end, $field)
 709:     {
 710:         return $this->_getTimeObjectTurbaListFallback($start, $end, $field);
 711:     }
 712: 
 713:     /**
 714:      * Default implementation for obtaining a Turba_List to get TimeObjects
 715:      * out of.
 716:      *
 717:      * @param Horde_Date $start  The starting date.
 718:      * @param Horde_Date $end    The ending date.
 719:      * @param string $field      The address book field containing the
 720:      *                           timeObject information (birthday,
 721:      *                           anniversary).
 722:      *
 723:      * @return Turba_List  A list of objects.
 724:      * @throws Turba_Exception
 725:      */
 726:     protected function _getTimeObjectTurbaListFallback(Horde_Date $start, Horde_Date $end, $field)
 727:     {
 728:         return $this->search(array(), null, 'AND', array('name', $field, 'category'));
 729:     }
 730: 
 731:     /**
 732:      * Retrieves a set of objects from the source.
 733:      *
 734:      * @param array $objectIds  The unique ids of the objects to retrieve.
 735:      *
 736:      * @return array  The array of retrieved objects (Turba_Objects).
 737:      * @throws Turba_Exception
 738:      */
 739:     public function getObjects(array $objectIds)
 740:     {
 741:         $objects = $this->_read($this->map['__key'], $objectIds,
 742:                                 $this->getContactOwner(),
 743:                                 array_values($this->fields),
 744:                                 $this->toDriverKeys($this->getBlobs()));
 745:         if (!is_array($objects)) {
 746:             throw new Turba_Exception(_("Requested object not found."));
 747:         }
 748: 
 749:         $results = array();
 750:         foreach ($objects as $object) {
 751:             $object = $this->toTurbaKeys($object);
 752:             $done = false;
 753:             if (!empty($object['__type']) &&
 754:                 ucwords($object['__type']) != 'Object') {
 755:                 $class = 'Turba_Object_' . ucwords($object['__type']);
 756:                 if (class_exists($class)) {
 757:                     $results[] = new $class($this, $object);
 758:                     $done = true;
 759:                 }
 760:             }
 761:             if (!$done) {
 762:                 $results[] = new Turba_Object($this, $object);
 763:             }
 764:         }
 765: 
 766:         return $results;
 767:     }
 768: 
 769:     /**
 770:      * Retrieves one object from the source.
 771:      *
 772:      * @param string $objectId  The unique id of the object to retrieve.
 773:      *
 774:      * @return Turba_Object  The retrieved object.
 775:      * @throws Turba_Exception
 776:      */
 777:     public function getObject($objectId)
 778:     {
 779:         $result = $this->getObjects(array($objectId));
 780: 
 781:         if (empty($result[0])) {
 782:             throw new Turba_Exception('No results');
 783:         }
 784: 
 785:         $result = $result[0];
 786:         if (!isset($this->map['__owner'])) {
 787:             $result->attributes['__owner'] = $this->getContactOwner();
 788:         }
 789: 
 790:         return $result;
 791:     }
 792: 
 793:     /**
 794:      * Adds a new entry to the contact source.
 795:      *
 796:      * @param array $attributes  The attributes of the new object to add.
 797:      *
 798:      * @return string  The new __key value on success.
 799:      * @throws Turba_Exception
 800:      */
 801:     public function add(array $attributes)
 802:     {
 803:         /* Only set __type and __owner if they are not already set. */
 804:         if (!isset($attributes['__type'])) {
 805:             $attributes['__type'] = 'Object';
 806:         }
 807:         if (isset($this->map['__owner']) && !isset($attributes['__owner'])) {
 808:             $attributes['__owner'] = $this->getContactOwner();
 809:         }
 810: 
 811:         if (!isset($attributes['__uid'])) {
 812:             $attributes['__uid'] = $this->_makeUid();
 813:         }
 814: 
 815:         $key = $attributes['__key'] = $this->_makeKey($this->toDriverKeys($attributes));
 816:         $uid = $attributes['__uid'];
 817: 
 818:         $attributes = $this->toDriverKeys($attributes);
 819: 
 820:         $this->_add($attributes, $this->toDriverKeys($this->getBlobs()));
 821: 
 822:         /* Log the creation of this item in the history log. */
 823:         try {
 824:             $GLOBALS['injector']->getInstance('Horde_History')
 825:                 ->log('turba:' . $this->getName() . ':' . $uid,
 826:                       array('action' => 'add'), true);
 827:         } catch (Exception $e) {
 828:             Horde::logMessage($e, 'ERR');
 829:         }
 830: 
 831:         return $key;
 832:     }
 833: 
 834:     /**
 835:      * Returns ability of the backend to add new contacts.
 836:      *
 837:      * @return boolean  Can backend add?
 838:      */
 839:     public function canAdd()
 840:     {
 841:         return $this->_canAdd();
 842:     }
 843: 
 844:     /**
 845:      * Returns ability of the backend to add new contacts.
 846:      *
 847:      * @return boolean  Can backend add?
 848:      */
 849:     protected function _canAdd()
 850:     {
 851:         return false;
 852:     }
 853: 
 854:     /**
 855:      * Deletes the specified entry from the contact source.
 856:      *
 857:      * @param string $object_id  The ID of the object to delete.
 858:      *
 859:      * @throws Turba_Exception
 860:      */
 861:     public function delete($object_id)
 862:     {
 863:         $object = $this->getObject($object_id);
 864: 
 865:         if (!$object->hasPermission(Horde_Perms::DELETE)) {
 866:             throw new Turba_Exception(_("Permission denied"));
 867:         }
 868: 
 869:         $this->_delete($this->toDriver('__key'), $object_id);
 870: 
 871:         $own_contact = $GLOBALS['prefs']->getValue('own_contact');
 872:         if (!empty($own_contact)) {
 873:             @list($source, $id) = explode(';', $own_contact);
 874:             if ($id == $object_id) {
 875:                 $GLOBALS['prefs']->setValue('own_contact', '');
 876:             }
 877:         }
 878: 
 879:         /* Log the deletion of this item in the history log. */
 880:         if ($object->getValue('__uid')) {
 881:             try {
 882:                 $GLOBALS['injector']->getInstance('Horde_History')->log($object->getGuid(),
 883:                                                 array('action' => 'delete'),
 884:                                                 true);
 885:             } catch (Exception $e) {
 886:                 Horde::logMessage($e, 'ERR');
 887:             }
 888:         }
 889: 
 890:         return true;
 891:     }
 892: 
 893:     /**
 894:      * Deletes all contacts from an address book.
 895:      *
 896:      * @param string $sourceName  The identifier of the address book to
 897:      *                            delete.  If omitted, will clear the current
 898:      *                            user's 'default' address book for this
 899:      *                            source type.
 900:      *
 901:      * @throws Turba_Exception
 902:      */
 903:     public function deleteAll($sourceName = null)
 904:     {
 905:         if (!$this->hasCapability('delete_all')) {
 906:             throw new Turba_Exception('Not supported');
 907:         }
 908: 
 909:         $this->_deleteAll($sourceName);
 910:     }
 911: 
 912:     /**
 913:      * TODO
 914:      */
 915:     protected function _deleteAll()
 916:     {
 917:     }
 918: 
 919:     /**
 920:      * Modifies an existing entry in the contact source.
 921:      *
 922:      * @param Turba_Object $object  The object to update.
 923:      *
 924:      * @return string  The object id, possibly updated.
 925:      * @throws Turba_Exception
 926:      */
 927:     public function save(Turba_Object $object)
 928:     {
 929:         $object_id = $this->_save($object);
 930: 
 931:         /* Log the modification of this item in the history log. */
 932:         if ($object->getValue('__uid')) {
 933:             try {
 934:                 $GLOBALS['injector']->getInstance('Horde_History')->log($object->getGuid(),
 935:                                                 array('action' => 'modify'),
 936:                                                 true);
 937:             } catch (Exception $e) {
 938:                 Horde::logMessage($e, 'ERR');
 939:             }
 940:         }
 941: 
 942:         return $object_id;
 943:     }
 944: 
 945:     /**
 946:      * Returns the criteria available for this source except '__key'.
 947:      *
 948:      * @return array  An array containing the criteria.
 949:      */
 950:     public function getCriteria()
 951:     {
 952:         $criteria = $this->map;
 953:         unset($criteria['__key']);
 954: 
 955:         return $criteria;
 956:     }
 957: 
 958:     /**
 959:      * Returns all non-composite fields for this source. Useful for importing
 960:      * and exporting data, etc.
 961:      *
 962:      * @return array  The field list.
 963:      */
 964:     public function getFields()
 965:     {
 966:         return array_flip($this->fields);
 967:     }
 968: 
 969:     /**
 970:      * Exports a given Turba_Object as an iCalendar vCard.
 971:      *
 972:      * @param Turba_Object $object  Turba_Object.
 973:      * @param string $version       The vcard version to produce.
 974:      * @param array $fields         Hash of field names and
 975:      *                              Horde_SyncMl_Property properties with the
 976:      *                              requested fields.
 977:      * @param boolean $skipEmpty    Whether to skip empty fields.
 978:      *
 979:      * @return Horde_Icalendar_Vcard  A vcard object.
 980:      */
 981:     public function tovCard(Turba_Object $object, $version = '2.1',
 982:                             array $fields = null, $skipEmpty = false)
 983:     {
 984:         $hash = $object->getAttributes();
 985:         $vcard = new Horde_Icalendar_Vcard($version);
 986:         $formattedname = false;
 987:         $charset = ($version == '2.1')
 988:             ? array('CHARSET' => 'UTF-8')
 989:             : array();
 990: 
 991:         $haveDecodeHook = Horde::hookExists('decode_attribute', 'turba');
 992:         foreach ($hash as $key => $val) {
 993:             if ($skipEmpty && !strlen($val)) {
 994:                 continue;
 995:             }
 996:             if ($haveDecodeHook) {
 997:                 try {
 998:                     $val = Horde::callHook(
 999:                         'decode_attribute',
1000:                         array($key, $val, $object),
1001:                         'turba');
1002:                 } catch (Turba_Exception $e) {}
1003:             }
1004:             switch ($key) {
1005:             case 'name':
1006:                 if ($fields && !isset($fields['FN'])) {
1007:                     break;
1008:                 }
1009:                 $vcard->setAttribute('FN', $val, Horde_Mime::is8bit($val) ? $charset : array());
1010:                 $formattedname = true;
1011:                 break;
1012: 
1013:             case 'nickname':
1014:             case 'alias':
1015:                 $params = Horde_Mime::is8bit($val) ? $charset : array();
1016:                 if (!$fields || isset($fields['NICKNAME'])) {
1017:                     $vcard->setAttribute('NICKNAME', $val, $params);
1018:                 }
1019:                 if (!$fields || isset($fields['X-EPOCSECONDNAME'])) {
1020:                     $vcard->setAttribute('X-EPOCSECONDNAME', $val, $params);
1021:                 }
1022:                 break;
1023: 
1024:             case 'homeAddress':
1025:                 if ($fields &&
1026:                     (!isset($fields['LABEL']) ||
1027:                      (isset($fields['LABEL']->Params['TYPE']) &&
1028:                       !$this->_hasValEnum($fields['LABEL']->Params['TYPE']->ValEnum, 'HOME')))) {
1029:                     break;
1030:                 }
1031:                 if ($version == '2.1') {
1032:                     $vcard->setAttribute('LABEL', $val, array('HOME' => null));
1033:                 } else {
1034:                     $vcard->setAttribute('LABEL', $val, array('TYPE' => 'HOME'));
1035:                 }
1036:                 break;
1037: 
1038:             case 'workAddress':
1039:                 if ($fields &&
1040:                     (!isset($fields['LABEL']) ||
1041:                      (isset($fields['LABEL']->Params['TYPE']) &&
1042:                       !$this->_hasValEnum($fields['LABEL']->Params['TYPE']->ValEnum, 'WORK')))) {
1043:                     break;
1044:                 }
1045:                 if ($version == '2.1') {
1046:                     $vcard->setAttribute('LABEL', $val, array('WORK' => null));
1047:                 } else {
1048:                     $vcard->setAttribute('LABEL', $val, array('TYPE' => 'WORK'));
1049:                 }
1050:                 break;
1051: 
1052:             case 'otherAddress':
1053:                 if ($fields && !isset($fields['LABEL'])) {
1054:                     break;
1055:                 }
1056:                 $vcard->setAttribute('LABEL', $val);
1057:                 break;
1058: 
1059:             case 'phone':
1060:                 if ($fields && !isset($fields['TEL'])) {
1061:                     break;
1062:                 }
1063:                 $vcard->setAttribute('TEL', $val);
1064:                 break;
1065: 
1066:             case 'homePhone':
1067:                 if ($fields &&
1068:                     (!isset($fields['TEL']) ||
1069:                      (isset($fields['TEL']->Params['TYPE']) &&
1070:                       !$this->_hasValEnum($fields['TEL']->Params['TYPE']->ValEnum, 'HOME')))) {
1071:                     break;
1072:                 }
1073:                 if ($version == '2.1') {
1074:                     $vcard->setAttribute('TEL', $val, array('HOME' => null, 'VOICE' => null));
1075:                 } else {
1076:                     $vcard->setAttribute('TEL', $val, array('TYPE' => array('HOME', 'VOICE')));
1077:                 }
1078:                 break;
1079: 
1080:             case 'workPhone':
1081:                 if ($fields &&
1082:                     (!isset($fields['TEL']) ||
1083:                      (isset($fields['TEL']->Params['TYPE']) &&
1084:                       !$this->_hasValEnum($fields['TEL']->Params['TYPE']->ValEnum, 'WORK')))) {
1085:                     break;
1086:                 }
1087:                 if ($version == '2.1') {
1088:                     $vcard->setAttribute('TEL', $val, array('WORK' => null, 'VOICE' => null));
1089:                 } else {
1090:                     $vcard->setAttribute('TEL', $val, array('TYPE' => array('WORK', 'VOICE')));
1091:                 }
1092:                 break;
1093: 
1094:             case 'cellPhone':
1095:                 if ($fields &&
1096:                     (!isset($fields['TEL']) ||
1097:                      (isset($fields['TEL']->Params['TYPE']) &&
1098:                       !$this->_hasValEnum($fields['TEL']->Params['TYPE']->ValEnum, 'CELL')))) {
1099:                     break;
1100:                 }
1101:                 if ($version == '2.1') {
1102:                     $vcard->setAttribute('TEL', $val, array('CELL' => null, 'VOICE' => null));
1103:                 } else {
1104:                     $vcard->setAttribute('TEL', $val, array('TYPE' => array('CELL', 'VOICE')));
1105:                 }
1106:                 break;
1107: 
1108:             case 'homeCellPhone':
1109:                 $parameters = array();
1110:                 if ($fields) {
1111:                     if (!isset($fields['TEL'])) {
1112:                         break;
1113:                     }
1114:                     if (!isset($fields['TEL']->Params['TYPE']) ||
1115:                         $this->_hasValEnum($fields['TEL']->Params['TYPE']->ValEnum, 'CELL')) {
1116:                         if ($version == '2.1') {
1117:                             $parameters['CELL'] = null;
1118:                             $parameters['VOICE'] = null;
1119:                         } else {
1120:                             $parameters['TYPE'] = array('CELL', 'VOICE');
1121:                         }
1122:                     }
1123:                     if (!isset($fields['TEL']->Params['TYPE']) ||
1124:                         $this->_hasValEnum($fields['TEL']->Params['TYPE']->ValEnum, 'HOME')) {
1125:                         if ($version == '2.1') {
1126:                             $parameters['HOME'] = null;
1127:                             $parameters['VOICE'] = null;
1128:                         } else {
1129:                             $parameters['TYPE'] = array('HOME', 'VOICE');
1130:                         }
1131:                     }
1132:                     if (empty($parameters)) {
1133:                         break;
1134:                     }
1135:                 } else {
1136:                     if ($version == '2.1') {
1137:                         $parameters = array('CELL' => null, 'HOME' => null, 'VOICE' => null);
1138:                     } else {
1139:                         $parameters = array('TYPE' => array('CELL', 'HOME', 'VOICE'));
1140:                     }
1141:                 }
1142:                 $vcard->setAttribute('TEL', $val, $parameters);
1143:                 break;
1144: 
1145:             case 'workCellPhone':
1146:                 $parameters = array();
1147:                 if ($fields) {
1148:                     if (!isset($fields['TEL'])) {
1149:                         break;
1150:                     }
1151:                     if (!isset($fields['TEL']->Params['TYPE']) ||
1152:                         $this->_hasValEnum($fields['TEL']->Params['TYPE']->ValEnum, 'CELL')) {
1153:                         if ($version == '2.1') {
1154:                             $parameters['CELL'] = null;
1155:                             $parameters['VOICE'] = null;
1156:                         } else {
1157:                             $parameters['TYPE'] = array('CELL', 'VOICE');
1158:                         }
1159:                     }
1160:                     if (!isset($fields['TEL']->Params['TYPE']) ||
1161:                         $this->_hasValEnum($fields['TEL']->Params['TYPE']->ValEnum, 'WORK')) {
1162:                         if ($version == '2.1') {
1163:                             $parameters['WORK'] = null;
1164:                             $parameters['VOICE'] = null;
1165:                         } else {
1166:                             $parameters['TYPE'] = array('WORK', 'VOICE');
1167:                         }
1168:                     }
1169:                     if (empty($parameters)) {
1170:                         break;
1171:                     }
1172:                 } else {
1173:                     if ($version == '2.1') {
1174:                         $parameters = array('CELL' => null, 'WORK' => null, 'VOICE' => null);
1175:                     } else {
1176:                         $parameters = array('TYPE' => array('CELL', 'WORK', 'VOICE'));
1177:                     }
1178:                 }
1179:                 $vcard->setAttribute('TEL', $val, $parameters);
1180:                 break;
1181: 
1182:             case 'videoCall':
1183:                 if ($fields &&
1184:                     (!isset($fields['TEL']) ||
1185:                      (isset($fields['TEL']->Params['TYPE']) &&
1186:                       !$this->_hasValEnum($fields['TEL']->Params['TYPE']->ValEnum, 'VIDEO')))) {
1187:                     break;
1188:                 }
1189:                 if ($version == '2.1') {
1190:                     $vcard->setAttribute('TEL', $val, array('VIDEO' => null));
1191:                 } else {
1192:                     $vcard->setAttribute('TEL', $val, array('TYPE' => 'VIDEO'));
1193:                 }
1194:                 break;
1195: 
1196:             case 'homeVideoCall':
1197:                 $parameters = array();
1198:                 if ($fields) {
1199:                     if (!isset($fields['TEL'])) {
1200:                         break;
1201:                     }
1202:                     if (!isset($fields['TEL']->Params['TYPE']) ||
1203:                         $this->_hasValEnum($fields['TEL']->Params['TYPE']->ValEnum, 'VIDEO')) {
1204:                         if ($version == '2.1') {
1205:                             $parameters['VIDEO'] = null;
1206:                         } else {
1207:                             $parameters['TYPE'] = 'VIDEO';
1208:                         }
1209:                     }
1210:                     if (!isset($fields['TEL']->Params['TYPE']) ||
1211:                         $this->_hasValEnum($fields['TEL']->Params['TYPE']->ValEnum, 'HOME')) {
1212:                         if ($version == '2.1') {
1213:                             $parameters['HOME'] = null;
1214:                         } else {
1215:                             $parameters['TYPE'] = 'HOME';
1216:                         }
1217:                     }
1218:                     if (empty($parameters)) {
1219:                         break;
1220:                     }
1221:                 } else {
1222:                     if ($version == '2.1') {
1223:                         $parameters = array('VIDEO' => null, 'HOME' => null);
1224:                     } else {
1225:                         $parameters = array('TYPE' => array('VIDEO', 'HOME'));
1226:                     }
1227:                 }
1228:                 $vcard->setAttribute('TEL', $val, $parameters);
1229:                 break;
1230: 
1231:             case 'workVideoCall':
1232:                 $parameters = array();
1233:                 if ($fields) {
1234:                     if (!isset($fields['TEL'])) {
1235:                         break;
1236:                     }
1237:                     if (!isset($fields['TEL']->Params['TYPE']) ||
1238:                         $this->_hasValEnum($fields['TEL']->Params['TYPE']->ValEnum, 'VIDEO')) {
1239:                         if ($version == '2.1') {
1240:                             $parameters['VIDEO'] = null;
1241:                         } else {
1242:                             $parameters['TYPE'] = 'VIDEO';
1243:                         }
1244:                     }
1245:                     if (!isset($fields['TEL']->Params['TYPE']) ||
1246:                         $this->_hasValEnum($fields['TEL']->Params['TYPE']->ValEnum, 'WORK')) {
1247:                         if ($version == '2.1') {
1248:                             $parameters['WORK'] = null;
1249:                         } else {
1250:                             $parameters['TYPE'] = 'WORK';
1251:                         }
1252:                     }
1253:                     if (empty($parameters)) {
1254:                         break;
1255:                     }
1256:                 } else {
1257:                     if ($version == '2.1') {
1258:                         $parameters = array('VIDEO' => null, 'WORK' => null);
1259:                     } else {
1260:                         $parameters = array('TYPE' => array('VIDEO', 'WORK'));
1261:                     }
1262:                 }
1263:                 $vcard->setAttribute('TEL', $val, $parameters);
1264:                 break;
1265: 
1266:             case 'sip':
1267:                 if ($fields && !isset($fields['X-SIP'])) {
1268:                     break;
1269:                 }
1270:                 $vcard->setAttribute('X-SIP', $val);
1271:                 break;
1272:             case 'ptt':
1273:                 if ($fields &&
1274:                     (!isset($fields['X-SIP']) ||
1275:                      (isset($fields['X-SIP']->Params['TYPE']) &&
1276:                       !$this->_hasValEnum($fields['X-SIP']->Params['TYPE']->ValEnum, 'POC')))) {
1277:                     break;
1278:                 }
1279:                 if ($version == '2.1') {
1280:                     $vcard->setAttribute('X-SIP', $val, array('POC' => null));
1281:                 } else {
1282:                     $vcard->setAttribute('X-SIP', $val, array('TYPE' => 'POC'));
1283:                 }
1284:                 break;
1285: 
1286:             case 'voip':
1287:                 if ($fields &&
1288:                     (!isset($fields['X-SIP']) ||
1289:                      (isset($fields['X-SIP']->Params['TYPE']) &&
1290:                       !$this->_hasValEnum($fields['X-SIP']->Params['TYPE']->ValEnum, 'VOIP')))) {
1291:                     break;
1292:                 }
1293:                 if ($version == '2.1') {
1294:                     $vcard->setAttribute('X-SIP', $val, array('VOIP' => null));
1295:                 } else {
1296:                     $vcard->setAttribute('X-SIP', $val, array('TYPE' => 'VOIP'));
1297:                 }
1298:                 break;
1299: 
1300:             case 'shareView':
1301:                 if ($fields &&
1302:                     (!isset($fields['X-SIP']) ||
1303:                      (isset($fields['X-SIP']->Params['TYPE']) &&
1304:                       !$this->_hasValEnum($fields['X-SIP']->Params['TYPE']->ValEnum, 'SWIS')))) {
1305:                     break;
1306:                 }
1307:                 if ($version == '2.1') {
1308:                     $vcard->setAttribute('X-SIP', $val, array('SWIS' => null));
1309:                 } else {
1310:                     $vcard->setAttribute('X-SIP', $val, array('TYPE' => 'SWIS'));
1311:                 }
1312:                 break;
1313: 
1314:             case 'instantMessenger':
1315:                 if ($fields && !isset($fields['X-WV-ID'])) {
1316:                     break;
1317:                 }
1318:                 $vcard->setAttribute('X-WV-ID', $val);
1319:                 break;
1320: 
1321:             case 'fax':
1322:                 if ($fields &&
1323:                     (!isset($fields['TEL']) ||
1324:                      (isset($fields['TEL']->Params['TYPE']) &&
1325:                       !$this->_hasValEnum($fields['TEL']->Params['TYPE']->ValEnum, 'FAX')))) {
1326:                     break;
1327:                 }
1328:                 if ($version == '2.1') {
1329:                     $vcard->setAttribute('TEL', $val, array('FAX' => null));
1330:                 } else {
1331:                     $vcard->setAttribute('TEL', $val, array('TYPE' => 'FAX'));
1332:                 }
1333:                 break;
1334: 
1335:             case 'homeFax':
1336:                 $parameters = array();
1337:                 if ($fields) {
1338:                     if (!isset($fields['TEL'])) {
1339:                         break;
1340:                     }
1341:                     if (!isset($fields['TEL']->Params['TYPE']) ||
1342:                         $this->_hasValEnum($fields['TEL']->Params['TYPE']->ValEnum, 'FAX')) {
1343:                         if ($version == '2.1') {
1344:                             $parameters['FAX'] = null;
1345:                         } else {
1346:                             $parameters['TYPE'] = 'FAX';
1347:                         }
1348:                     }
1349:                     if (!isset($fields['TEL']->Params['TYPE']) ||
1350:                         $this->_hasValEnum($fields['TEL']->Params['TYPE']->ValEnum, 'HOME')) {
1351:                         if ($version == '2.1') {
1352:                             $parameters['HOME'] = null;
1353:                         } else {
1354:                             $parameters['TYPE'] = 'HOME';
1355:                         }
1356:                     }
1357:                     if (empty($parameters)) {
1358:                         break;
1359:                     }
1360:                 } else {
1361:                     if ($version == '2.1') {
1362:                         $parameters = array('FAX' => null, 'HOME' => null);
1363:                     } else {
1364:                         $parameters = array('TYPE' => array('FAX', 'HOME'));
1365:                     }
1366:                 }
1367:                 $vcard->setAttribute('TEL', $val, $parameters);
1368:                 break;
1369: 
1370:             case 'workFax':
1371:                 $parameters = array();
1372:                 if ($fields) {
1373:                     if (!isset($fields['TEL'])) {
1374:                         break;
1375:                     }
1376:                     if (!isset($fields['TEL']->Params['TYPE']) ||
1377:                         $this->_hasValEnum($fields['TEL']->Params['TYPE']->ValEnum, 'FAX')) {
1378:                         if ($version == '2.1') {
1379:                             $parameters['FAX'] = null;
1380:                         } else {
1381:                             $parameters['TYPE'] = 'FAX';
1382:                         }
1383:                     }
1384:                     if (!isset($fields['TEL']->Params['TYPE']) ||
1385:                         $this->_hasValEnum($fields['TEL']->Params['TYPE']->ValEnum, 'WORK')) {
1386:                         if ($version == '2.1') {
1387:                             $parameters['WORK'] = null;
1388:                         } else {
1389:                             $parameters['TYPE'] = 'WORK';
1390:                         }
1391:                     }
1392:                     if (empty($parameters)) {
1393:                         break;
1394:                     }
1395:                 } else {
1396:                     if ($version == '2.1') {
1397:                         $parameters = array('FAX' => null, 'WORK' => null);
1398:                     } else {
1399:                         $parameters = array('TYPE' => array('FAX', 'WORK'));
1400:                     }
1401:                 }
1402:                 $vcard->setAttribute('TEL', $val, $parameters);
1403:                 if ($version == '2.1') {
1404:                     $vcard->setAttribute('TEL', $val, array('FAX' => null, 'WORK' => null));
1405:                 } else {
1406:                     $vcard->setAttribute('TEL', $val, array('TYPE' => array('FAX', 'WORK')));
1407:                 }
1408:                 break;
1409: 
1410:             case 'pager':
1411:                 if ($fields &&
1412:                     (!isset($fields['TEL']) ||
1413:                      (isset($fields['TEL']->Params['TYPE']) &&
1414:                       !$this->_hasValEnum($fields['TEL']->Params['TYPE']->ValEnum, 'PAGER')))) {
1415:                     break;
1416:                 }
1417:                 if ($version == '2.1') {
1418:                     $vcard->setAttribute('TEL', $val, array('PAGER' => null));
1419:                 } else {
1420:                     $vcard->setAttribute('TEL', $val, array('TYPE' => 'PAGER'));
1421:                 }
1422:                 break;
1423: 
1424:             case 'email':
1425:                 if ($fields && !isset($fields['EMAIL'])) {
1426:                     break;
1427:                 }
1428:                 if ($version == '2.1') {
1429:                     $vcard->setAttribute(
1430:                         'EMAIL',
1431:                         Horde_Icalendar_Vcard::getBareEmail($val),
1432:                         array('INTERNET' => null));
1433:                 } else {
1434:                     $vcard->setAttribute(
1435:                         'EMAIL',
1436:                         Horde_Icalendar_Vcard::getBareEmail($val),
1437:                         array('TYPE' => 'INTERNET'));
1438:                 }
1439:                 break;
1440: 
1441:             case 'homeEmail':
1442:                 if ($fields &&
1443:                     (!isset($fields['EMAIL']) ||
1444:                      (isset($fields['EMAIL']->Params['TYPE']) &&
1445:                       !$this->_hasValEnum($fields['EMAIL']->Params['TYPE']->ValEnum, 'HOME')))) {
1446:                     break;
1447:                 }
1448:                 if ($version == '2.1') {
1449:                     $vcard->setAttribute('EMAIL',
1450:                                          Horde_Icalendar_Vcard::getBareEmail($val),
1451:                                          array('HOME' => null));
1452:                 } else {
1453:                     $vcard->setAttribute('EMAIL',
1454:                                          Horde_Icalendar_Vcard::getBareEmail($val),
1455:                                          array('TYPE' => 'HOME'));
1456:                 }
1457:                 break;
1458: 
1459:             case 'workEmail':
1460:                 if ($fields &&
1461:                     (!isset($fields['EMAIL']) ||
1462:                      (isset($fields['EMAIL']->Params['TYPE']) &&
1463:                       !$this->_hasValEnum($fields['EMAIL']->Params['TYPE']->ValEnum, 'WORK')))) {
1464:                     break;
1465:                 }
1466:                 if ($version == '2.1') {
1467:                     $vcard->setAttribute('EMAIL',
1468:                                          Horde_Icalendar_Vcard::getBareEmail($val),
1469:                                          array('WORK' => null));
1470:                 } else {
1471:                     $vcard->setAttribute('EMAIL',
1472:                                          Horde_Icalendar_Vcard::getBareEmail($val),
1473:                                          array('TYPE' => 'WORK'));
1474:                 }
1475:                 break;
1476: 
1477:             case 'emails':
1478:                 if ($fields && !isset($fields['EMAIL'])) {
1479:                     break;
1480:                 }
1481:                 $emails = explode(',', $val);
1482:                 foreach ($emails as $email) {
1483:                     $vcard->setAttribute('EMAIL', Horde_Icalendar_Vcard::getBareEmail($email));
1484:                 }
1485:                 break;
1486: 
1487:             case 'title':
1488:                 if ($fields && !isset($fields['TITLE'])) {
1489:                     break;
1490:                 }
1491:                 $vcard->setAttribute('TITLE', $val, Horde_Mime::is8bit($val) ? $charset : array());
1492:                 break;
1493: 
1494:             case 'role':
1495:                 if ($fields && !isset($fields['ROLE'])) {
1496:                     break;
1497:                 }
1498:                 $vcard->setAttribute('ROLE', $val, Horde_Mime::is8bit($val) ? $charset : array());
1499:                 break;
1500: 
1501:             case 'notes':
1502:                 if ($fields && !isset($fields['NOTE'])) {
1503:                     break;
1504:                 }
1505:                 $vcard->setAttribute('NOTE', $val, Horde_Mime::is8bit($val) ? $charset : array());
1506:                 break;
1507: 
1508:             case 'businessCategory':
1509:             case 'category':
1510:                 if ($fields && !isset($fields['CATEGORIES'])) {
1511:                     break;
1512:                 }
1513:                 $vcard->setAttribute('CATEGORIES', $val);
1514:                 break;
1515: 
1516:             case 'anniversary':
1517:                 if (!$fields || isset($fields['X-ANNIVERSARY'])) {
1518:                     $vcard->setAttribute('X-ANNIVERSARY', $val);
1519:                 }
1520:                 break;
1521: 
1522:             case 'spouse':
1523:                 if (!$fields || isset($fields['X-SPOUSE'])) {
1524:                     $vcard->setAttribute('X-SPOUSE', $val);
1525:                 }
1526:                 break;
1527: 
1528:             case 'children':
1529:                 if (!$fields || isset($fields['X-CHILDREN'])) {
1530:                     $vcard->setAttribute('X-CHILDREN', $val);
1531:                 }
1532:                 break;
1533: 
1534:             case 'website':
1535:                 if ($fields && !isset($fields['URL'])) {
1536:                     break;
1537:                 }
1538:                 $vcard->setAttribute('URL', $val);
1539:                 break;
1540: 
1541:             case 'homeWebsite':
1542:                 if ($fields &&
1543:                     (!isset($fields['URL']) ||
1544:                      (isset($fields['URL']->Params['TYPE']) &&
1545:                       !$this->_hasValEnum($fields['URL']->Params['TYPE']->ValEnum, 'HOME')))) {
1546:                     break;
1547:                 }
1548:                 if ($version == '2.1') {
1549:                     $vcard->setAttribute('URL', $val, array('HOME' => null));
1550:                 } else {
1551:                     $vcard->setAttribute('URL', $val, array('TYPE' => 'HOME'));
1552:                 }
1553:                 break;
1554: 
1555:             case 'workWebsite':
1556:                 if ($fields &&
1557:                     (!isset($fields['URL']) ||
1558:                      (isset($fields['URL']->Params['TYPE']) &&
1559:                       !$this->_hasValEnum($fields['URL']->Params['TYPE']->ValEnum, 'WORK')))) {
1560:                     break;
1561:                 }
1562:                 if ($version == '2.1') {
1563:                     $vcard->setAttribute('URL', $val, array('WORK' => null));
1564:                 } else {
1565:                     $vcard->setAttribute('URL', $val, array('TYPE' => 'WORK'));
1566:                 }
1567:                 break;
1568: 
1569:             case 'birthday':
1570:                 if ($fields && !isset($fields['BDAY'])) {
1571:                     break;
1572:                 }
1573:                 $vcard->setAttribute('BDAY', $val);
1574:                 break;
1575: 
1576:             case 'timezone':
1577:                 if ($fields && !isset($fields['TZ'])) {
1578:                     break;
1579:                 }
1580:                 $vcard->setAttribute('TZ', $val, array('VALUE' => 'text'));
1581:                 break;
1582: 
1583:             case 'latitude':
1584:                 if ($fields && !isset($fields['GEO'])) {
1585:                     break;
1586:                 }
1587:                 if (isset($hash['longitude'])) {
1588:                     $vcard->setAttribute('GEO',
1589:                                          array('latitude' => $val,
1590:                                                'longitude' => $hash['longitude']));
1591:                 }
1592:                 break;
1593: 
1594:             case 'homeLatitude':
1595:                 if ($fields &&
1596:                     (!isset($fields['GEO']) ||
1597:                      (isset($fields['GEO']->Params['TYPE']) &&
1598:                       !$this->_hasValEnum($fields['GEO']->Params['TYPE']->ValEnum, 'HOME')))) {
1599:                     break;
1600:                 }
1601:                 if (isset($hash['homeLongitude'])) {
1602:                     if ($version == '2.1') {
1603:                         $vcard->setAttribute('GEO',
1604:                                              array('latitude' => $val,
1605:                                                    'longitude' => $hash['homeLongitude']),
1606:                                              array('HOME' => null));
1607:                    } else {
1608:                         $vcard->setAttribute('GEO',
1609:                                              array('latitude' => $val,
1610:                                                    'longitude' => $hash['homeLongitude']),
1611:                                              array('TYPE' => 'HOME'));
1612:                    }
1613:                 }
1614:                 break;
1615: 
1616:             case 'workLatitude':
1617:                 if ($fields &&
1618:                     (!isset($fields['GEO']) ||
1619:                      (isset($fields['GEO']->Params['TYPE']) &&
1620:                       !$this->_hasValEnum($fields['GEO']->Params['TYPE']->ValEnum, 'HOME')))) {
1621:                     break;
1622:                 }
1623:                 if (isset($hash['workLongitude'])) {
1624:                     if ($version == '2.1') {
1625:                         $vcard->setAttribute('GEO',
1626:                                              array('latitude' => $val,
1627:                                                    'longitude' => $hash['workLongitude']),
1628:                                              array('WORK' => null));
1629:                    } else {
1630:                         $vcard->setAttribute('GEO',
1631:                                              array('latitude' => $val,
1632:                                                    'longitude' => $hash['workLongitude']),
1633:                                              array('TYPE' => 'WORK'));
1634:                    }
1635:                 }
1636:                 break;
1637: 
1638:             case 'photo':
1639:             case 'logo':
1640:                 $name = Horde_String::upper($key);
1641:                 $params = array();
1642:                 if (strlen($val)) {
1643:                     $params['ENCODING'] = 'b';
1644:                 }
1645:                 if (isset($hash[$key . 'type'])) {
1646:                     $params['TYPE'] = $hash[$key . 'type'];
1647:                 }
1648:                 if ($fields &&
1649:                     (!isset($fields[$name]) ||
1650:                      (isset($params['TYPE']) &&
1651:                       isset($fields[$name]->Params['TYPE']) &&
1652:                       !$this->_hasValEnum($fields[$name]->Params['TYPE']->ValEnum, $params['TYPE'])))) {
1653:                     break;
1654:                 }
1655:                 $vcard->setAttribute($name,
1656:                                      base64_encode($val),
1657:                                      $params);
1658:                 break;
1659:             }
1660:         }
1661: 
1662:         // No explicit firstname/lastname in data source: we have to guess.
1663:         if (!isset($hash['lastname']) && isset($hash['name'])) {
1664:             $this->_guessName($hash);
1665:         }
1666: 
1667:         $a = array(
1668:             Horde_Icalendar_Vcard::N_FAMILY => isset($hash['lastname']) ? $hash['lastname'] : '',
1669:             Horde_Icalendar_Vcard::N_GIVEN  => isset($hash['firstname']) ? $hash['firstname'] : '',
1670:             Horde_Icalendar_Vcard::N_ADDL   => isset($hash['middlenames']) ? $hash['middlenames'] : '',
1671:             Horde_Icalendar_Vcard::N_PREFIX => isset($hash['namePrefix']) ? $hash['namePrefix'] : '',
1672:             Horde_Icalendar_Vcard::N_SUFFIX => isset($hash['nameSuffix']) ? $hash['nameSuffix'] : '',
1673:         );
1674:         $val = implode(';', $a);
1675:         if (!$fields || isset($fields['N'])) {
1676:             $vcard->setAttribute('N', $val, Horde_Mime::is8bit($val) ? $charset : array(), false, $a);
1677:         }
1678: 
1679:         if (!$formattedname && (!$fields || isset($fields['FN']))) {
1680:             if ($object->getValue('name')) {
1681:                 $val = $object->getValue('name');
1682:             } elseif (!empty($this->alternativeName) &&
1683:                 isset($hash[$this->alternativeName])) {
1684:                 $val = $hash[$this->alternativeName];
1685:             } else {
1686:                 $val = '';
1687:             }
1688:             $vcard->setAttribute('FN', $val, Horde_Mime::is8bit($val) ? $charset : array());
1689:         }
1690: 
1691:         $org = array();
1692:         if (!empty($hash['company']) ||
1693:             (!$skipEmpty && array_key_exists('company', $hash))) {
1694:             $org[] = $hash['company'];
1695:         }
1696:         if (!empty($hash['department']) ||
1697:             (!$skipEmpty && array_key_exists('department', $hash))) {
1698:             $org[] = $hash['department'];
1699:         }
1700:         if (count($org) && (!$fields || isset($fields['ORG']))) {
1701:             $val = implode(';', $org);
1702:             $vcard->setAttribute('ORG', $val, Horde_Mime::is8bit($val) ? $charset : array(), false, $org);
1703:         }
1704: 
1705:         if ((!$fields || isset($fields['ADR'])) &&
1706:             (!empty($hash['commonAddress']) ||
1707:              !empty($hash['commonStreet']) ||
1708:              !empty($hash['commonPOBox']) ||
1709:              !empty($hash['commonExtended']) ||
1710:              !empty($hash['commonCity']) ||
1711:              !empty($hash['commonProvince']) ||
1712:              !empty($hash['commonPostalCode']) ||
1713:              !empty($hash['commonCountry']) ||
1714:              (!$skipEmpty &&
1715:               (array_key_exists('commonAddress', $hash) ||
1716:                array_key_exists('commonStreet', $hash) ||
1717:                array_key_exists('commonPOBox', $hash) ||
1718:                array_key_exists('commonExtended', $hash) ||
1719:                array_key_exists('commonCity', $hash) ||
1720:                array_key_exists('commonProvince', $hash) ||
1721:                array_key_exists('commonPostalCode', $hash) ||
1722:                array_key_exists('commonCountry', $hash))))) {
1723:             /* We can't know if this particular Turba source uses a single
1724:              * address field or multiple for
1725:              * street/city/province/postcode/country. Try to deal with
1726:              * both. */
1727:             if (isset($hash['commonAddress']) &&
1728:                 !isset($hash['commonStreet'])) {
1729:                 $hash['commonStreet'] = $hash['commonAddress'];
1730:             }
1731:             $a = array(
1732:                 Horde_Icalendar_Vcard::ADR_POB      => isset($hash['commonPOBox'])
1733:                     ? $hash['commonPOBox'] : '',
1734:                 Horde_Icalendar_Vcard::ADR_EXTEND   => isset($hash['commonExtended'])
1735:                     ? $hash['commonExtended'] : '',
1736:                 Horde_Icalendar_Vcard::ADR_STREET   => isset($hash['commonStreet'])
1737:                     ? $hash['commonStreet'] : '',
1738:                 Horde_Icalendar_Vcard::ADR_LOCALITY => isset($hash['commonCity'])
1739:                     ? $hash['commonCity'] : '',
1740:                 Horde_Icalendar_Vcard::ADR_REGION   => isset($hash['commonProvince'])
1741:                     ? $hash['commonProvince'] : '',
1742:                 Horde_Icalendar_Vcard::ADR_POSTCODE => isset($hash['commonPostalCode'])
1743:                     ? $hash['commonPostalCode'] : '',
1744:                 Horde_Icalendar_Vcard::ADR_COUNTRY  => isset($hash['commonCountry'])
1745:                     ? Horde_Nls::getCountryISO($hash['commonCountry']) : '',
1746:             );
1747: 
1748:             $val = implode(';', $a);
1749:             if ($version == '2.1') {
1750:                 $params = array();
1751:                 if (Horde_Mime::is8bit($val)) {
1752:                     $params['CHARSET'] = 'UTF-8';
1753:                 }
1754:             } else {
1755:                 $params = array('TYPE' => '');
1756:             }
1757:             $vcard->setAttribute('ADR', $val, $params, true, $a);
1758:         }
1759: 
1760:         if ((!$fields ||
1761:              (isset($fields['ADR']) &&
1762:               (!isset($fields['ADR']->Params['TYPE']) ||
1763:                $this->_hasValEnum($fields['ADR']->Params['TYPE']->ValEnum, 'HOME')))) &&
1764:             (!empty($hash['homeAddress']) ||
1765:              !empty($hash['homeStreet']) ||
1766:              !empty($hash['homePOBox']) ||
1767:              !empty($hash['homeExtended']) ||
1768:              !empty($hash['homeCity']) ||
1769:              !empty($hash['homeProvince']) ||
1770:              !empty($hash['homePostalCode']) ||
1771:              !empty($hash['homeCountry']) ||
1772:              (!$skipEmpty &&
1773:               (array_key_exists('homeAddress', $hash) ||
1774:                array_key_exists('homeStreet', $hash) ||
1775:                array_key_exists('homePOBox', $hash) ||
1776:                array_key_exists('homeExtended', $hash) ||
1777:                array_key_exists('homeCity', $hash) ||
1778:                array_key_exists('homeProvince', $hash) ||
1779:                array_key_exists('homePostalCode', $hash) ||
1780:                array_key_exists('homeCountry', $hash))))) {
1781:             if (isset($hash['homeAddress']) && !isset($hash['homeStreet'])) {
1782:                 $hash['homeStreet'] = $hash['homeAddress'];
1783:             }
1784:             $a = array(
1785:                 Horde_Icalendar_Vcard::ADR_POB      => isset($hash['homePOBox'])
1786:                     ? $hash['homePOBox'] : '',
1787:                 Horde_Icalendar_Vcard::ADR_EXTEND   => isset($hash['homeExtended'])
1788:                     ? $hash['homeExtended'] : '',
1789:                 Horde_Icalendar_Vcard::ADR_STREET   => isset($hash['homeStreet'])
1790:                     ? $hash['homeStreet'] : '',
1791:                 Horde_Icalendar_Vcard::ADR_LOCALITY => isset($hash['homeCity'])
1792:                     ? $hash['homeCity'] : '',
1793:                 Horde_Icalendar_Vcard::ADR_REGION   => isset($hash['homeProvince'])
1794:                     ? $hash['homeProvince'] : '',
1795:                 Horde_Icalendar_Vcard::ADR_POSTCODE => isset($hash['homePostalCode'])
1796:                     ? $hash['homePostalCode'] : '',
1797:                 Horde_Icalendar_Vcard::ADR_COUNTRY  => isset($hash['homeCountry'])
1798:                     ? Horde_Nls::getCountryISO($hash['homeCountry']) : '',
1799:             );
1800: 
1801:             $val = implode(';', $a);
1802:             if ($version == '2.1') {
1803:                 $params = array('HOME' => null);
1804:                 if (Horde_Mime::is8bit($val)) {
1805:                     $params['CHARSET'] = 'UTF-8';
1806:                 }
1807:             } else {
1808:                 $params = array('TYPE' => 'HOME');
1809:             }
1810:             $vcard->setAttribute('ADR', $val, $params, true, $a);
1811:         }
1812: 
1813:         if ((!$fields ||
1814:              (isset($fields['ADR']) &&
1815:               (!isset($fields['ADR']->Params['TYPE']) ||
1816:                $this->_hasValEnum($fields['ADR']->Params['TYPE']->ValEnum, 'WORK')))) &&
1817:             (!empty($hash['workAddress']) ||
1818:              !empty($hash['workStreet']) ||
1819:              !empty($hash['workPOBox']) ||
1820:              !empty($hash['workExtended']) ||
1821:              !empty($hash['workCity']) ||
1822:              !empty($hash['workProvince']) ||
1823:              !empty($hash['workPostalCode']) ||
1824:              !empty($hash['workCountry']) ||
1825:              (!$skipEmpty &&
1826:               (array_key_exists('workAddress', $hash) ||
1827:                array_key_exists('workStreet', $hash) ||
1828:                array_key_exists('workPOBox', $hash) ||
1829:                array_key_exists('workExtended', $hash) ||
1830:                array_key_exists('workCity', $hash) ||
1831:                array_key_exists('workProvince', $hash) ||
1832:                array_key_exists('workPostalCode', $hash) ||
1833:                array_key_exists('workCountry', $hash))))) {
1834:             if (isset($hash['workAddress']) && !isset($hash['workStreet'])) {
1835:                 $hash['workStreet'] = $hash['workAddress'];
1836:             }
1837:             $a = array(
1838:                 Horde_Icalendar_Vcard::ADR_POB      => isset($hash['workPOBox'])
1839:                     ? $hash['workPOBox'] : '',
1840:                 Horde_Icalendar_Vcard::ADR_EXTEND   => isset($hash['workExtended'])
1841:                     ? $hash['workExtended'] : '',
1842:                 Horde_Icalendar_Vcard::ADR_STREET   => isset($hash['workStreet'])
1843:                     ? $hash['workStreet'] : '',
1844:                 Horde_Icalendar_Vcard::ADR_LOCALITY => isset($hash['workCity'])
1845:                     ? $hash['workCity'] : '',
1846:                 Horde_Icalendar_Vcard::ADR_REGION   => isset($hash['workProvince'])
1847:                     ? $hash['workProvince'] : '',
1848:                 Horde_Icalendar_Vcard::ADR_POSTCODE => isset($hash['workPostalCode'])
1849:                     ? $hash['workPostalCode'] : '',
1850:                 Horde_Icalendar_Vcard::ADR_COUNTRY  => isset($hash['workCountry'])
1851:                     ? Horde_Nls::getCountryISO($hash['workCountry']) : '',
1852:             );
1853: 
1854:             $val = implode(';', $a);
1855:             if ($version == '2.1') {
1856:                 $params = array('WORK' => null);
1857:                 if (Horde_Mime::is8bit($val)) {
1858:                     $params['CHARSET'] = 'UTF-8';
1859:                 }
1860:             } else {
1861:                 $params = array('TYPE' => 'WORK');
1862:             }
1863:             $vcard->setAttribute('ADR', $val, $params, true, $a);
1864:         }
1865: 
1866:         return $vcard;
1867:     }
1868: 
1869:     /**
1870:      * Returns whether a ValEnum entry from a DevInf object contains a certain
1871:      * type.
1872:      *
1873:      * @param array $valEnum  A ValEnum hash.
1874:      * @param string $type    A requested attribute type.
1875:      *
1876:      * @return boolean  True if $type exists in $valEnum.
1877:      */
1878:     protected function _hasValEnum($valEnum, $type)
1879:     {
1880:         foreach (array_keys($valEnum) as $key) {
1881:             if (in_array($type, explode(',', $key))) {
1882:                 return true;
1883:             }
1884:         }
1885:         return false;
1886:     }
1887: 
1888:     /**
1889:      * Function to convert a Horde_Icalendar_Vcard object into a Turba
1890:      * Object Hash with Turba attributes suitable as a parameter for add().
1891:      *
1892:      * @see add()
1893:      *
1894:      * @param Horde_Icalendar_Vcard $vcard  The Horde_Icalendar_Vcard object
1895:      *                                      to parse.
1896:      *
1897:      * @return array  A Turba attribute hash.
1898:      */
1899:     public function toHash(Horde_Icalendar_Vcard $vcard)
1900:     {
1901:         $hash = array();
1902:         $attr = $vcard->getAllAttributes();
1903:         foreach ($attr as $item) {
1904:             switch ($item['name']) {
1905:             case 'FN':
1906:                 $hash['name'] = $item['value'];
1907:                 break;
1908: 
1909:             case 'N':
1910:                 $name = $item['values'];
1911:                 if (!empty($name[Horde_Icalendar_Vcard::N_FAMILY])) {
1912:                     $hash['lastname'] = $name[Horde_Icalendar_Vcard::N_FAMILY];
1913:                 }
1914:                 if (!empty($name[Horde_Icalendar_Vcard::N_GIVEN])) {
1915:                     $hash['firstname'] = $name[Horde_Icalendar_Vcard::N_GIVEN];
1916:                 }
1917:                 if (!empty($name[Horde_Icalendar_Vcard::N_ADDL])) {
1918:                     $hash['middlenames'] = $name[Horde_Icalendar_Vcard::N_ADDL];
1919:                 }
1920:                 if (!empty($name[Horde_Icalendar_Vcard::N_PREFIX])) {
1921:                     $hash['namePrefix'] = $name[Horde_Icalendar_Vcard::N_PREFIX];
1922:                 }
1923:                 if (!empty($name[Horde_Icalendar_Vcard::N_SUFFIX])) {
1924:                     $hash['nameSuffix'] = $name[Horde_Icalendar_Vcard::N_SUFFIX];
1925:                 }
1926:                 break;
1927: 
1928:             case 'NICKNAME':
1929:             case 'X-EPOCSECONDNAME':
1930:                 $hash['nickname'] = $item['value'];
1931:                 $hash['alias'] = $item['value'];
1932:                 break;
1933: 
1934:             // We use LABEL but also support ADR.
1935:             case 'LABEL':
1936:                 if (isset($item['params']['HOME']) && !isset($hash['homeAddress'])) {
1937:                     $hash['homeAddress'] = $item['value'];
1938:                 } elseif (isset($item['params']['WORK']) && !isset($hash['workAddress'])) {
1939:                     $hash['workAddress'] = $item['value'];
1940:                 } elseif (!isset($hash['commonAddress'])) {
1941:                     $hash['commonAddress'] = $item['value'];
1942:                 }
1943:                 break;
1944: 
1945:             case 'ADR':
1946:                 if (isset($item['params']['TYPE'])) {
1947:                     if (!is_array($item['params']['TYPE'])) {
1948:                         $item['params']['TYPE'] = array($item['params']['TYPE']);
1949:                     }
1950:                 } else {
1951:                     $item['params']['TYPE'] = array();
1952:                     if (isset($item['params']['WORK'])) {
1953:                         $item['params']['TYPE'][] = 'WORK';
1954:                     }
1955:                     if (isset($item['params']['HOME'])) {
1956:                         $item['params']['TYPE'][] = 'HOME';
1957:                     }
1958:                     if (count($item['params']['TYPE']) == 0) {
1959:                         $item['params']['TYPE'][] = 'COMMON';
1960:                     }
1961:                 }
1962: 
1963:                 $address = $item['values'];
1964:                 foreach ($item['params']['TYPE'] as $adr) {
1965:                     switch (Horde_String::upper($adr)) {
1966:                     case 'HOME':
1967:                         $prefix = 'home';
1968:                         break;
1969: 
1970:                     case 'WORK':
1971:                         $prefix = 'work';
1972:                         break;
1973: 
1974:                     default:
1975:                         $prefix = 'common';
1976:                     }
1977: 
1978:                     if (isset($hash[$prefix . 'Address'])) {
1979:                         continue;
1980:                     }
1981: 
1982:                     $hash[$prefix . 'Address'] = '';
1983: 
1984:                     if (!empty($address[Horde_Icalendar_Vcard::ADR_STREET])) {
1985:                         $hash[$prefix . 'Street'] = $address[Horde_Icalendar_Vcard::ADR_STREET];
1986:                         $hash[$prefix . 'Address'] .= $hash[$prefix . 'Street'] . "\n";
1987:                     }
1988:                     if (!empty($address[Horde_Icalendar_Vcard::ADR_EXTEND])) {
1989:                         $hash[$prefix . 'Extended'] = $address[Horde_Icalendar_Vcard::ADR_EXTEND];
1990:                         $hash[$prefix . 'Address'] .= $hash[$prefix . 'Extended'] . "\n";
1991:                     }
1992:                     if (!empty($address[Horde_Icalendar_Vcard::ADR_POB])) {
1993:                         $hash[$prefix . 'POBox'] = $address[Horde_Icalendar_Vcard::ADR_POB];
1994:                         $hash[$prefix . 'Address'] .= $hash[$prefix . 'POBox'] . "\n";
1995:                     }
1996:                     if (!empty($address[Horde_Icalendar_Vcard::ADR_LOCALITY])) {
1997:                         $hash[$prefix . 'City'] = $address[Horde_Icalendar_Vcard::ADR_LOCALITY];
1998:                         $hash[$prefix . 'Address'] .= $hash[$prefix . 'City'];
1999:                     }
2000:                     if (!empty($address[Horde_Icalendar_Vcard::ADR_REGION])) {
2001:                         $hash[$prefix . 'Province'] = $address[Horde_Icalendar_Vcard::ADR_REGION];
2002:                         $hash[$prefix . 'Address'] .= ', ' . $hash[$prefix . 'Province'];
2003:                     }
2004:                     if (!empty($address[Horde_Icalendar_Vcard::ADR_POSTCODE])) {
2005:                         $hash[$prefix . 'PostalCode'] = $address[Horde_Icalendar_Vcard::ADR_POSTCODE];
2006:                         $hash[$prefix . 'Address'] .= ' ' . $hash[$prefix . 'PostalCode'];
2007:                     }
2008:                     if (!empty($address[Horde_Icalendar_Vcard::ADR_COUNTRY])) {
2009:                         include 'Horde/Nls/Countries.php';
2010:                         $country = array_search($address[Horde_Icalendar_Vcard::ADR_COUNTRY], $countries);
2011:                         if ($country === false) {
2012:                             $country = $address[Horde_Icalendar_Vcard::ADR_COUNTRY];
2013:                         }
2014:                         $hash[$prefix . 'Country'] = $country;
2015:                         $hash[$prefix . 'Address'] .= "\n" . $address[Horde_Icalendar_Vcard::ADR_COUNTRY];
2016:                     }
2017: 
2018:                     $hash[$prefix . 'Address'] = trim($hash[$prefix . 'Address']);
2019:                 }
2020:                 break;
2021: 
2022:             case 'TZ':
2023:                 // We only support textual timezones.
2024:                 if (!isset($item['params']['VALUE']) ||
2025:                     Horde_String::lower($item['params']['VALUE']) != 'text') {
2026:                     break;
2027:                 }
2028:                 $timezones = explode(';', $item['value']);
2029:                 $available_timezones = Horde_Nls::getTimezones();
2030:                 foreach ($timezones as $timezone) {
2031:                     $timezone = trim($timezone);
2032:                     if (isset($available_timezones[$timezone])) {
2033:                         $hash['timezone'] = $timezone;
2034:                         break 2;
2035:                     }
2036:                 }
2037:                 break;
2038: 
2039:             case 'GEO':
2040:                 if (isset($item['params']['HOME'])) {
2041:                     $hash['homeLatitude'] = $item['value']['latitude'];
2042:                     $hash['homeLongitude'] = $item['value']['longitude'];
2043:                 } elseif (isset($item['params']['WORK'])) {
2044:                     $hash['workLatitude'] = $item['value']['latitude'];
2045:                     $hash['workLongitude'] = $item['value']['longitude'];
2046:                 } else {
2047:                     $hash['latitude'] = $item['value']['latitude'];
2048:                     $hash['longitude'] = $item['value']['longitude'];
2049:                 }
2050:                 break;
2051: 
2052:             case 'TEL':
2053:                 if (isset($item['params']['FAX'])) {
2054:                     if (isset($item['params']['WORK']) &&
2055:                         !isset($hash['workFax'])) {
2056:                         $hash['workFax'] = $item['value'];
2057:                     } elseif (isset($item['params']['HOME']) &&
2058:                               !isset($hash['homeFax'])) {
2059:                         $hash['homeFax'] = $item['value'];
2060:                     } elseif (!isset($hash['fax'])) {
2061:                         $hash['fax'] = $item['value'];
2062:                     }
2063:                 } elseif (isset($item['params']['PAGER']) &&
2064:                           !isset($hash['pager'])) {
2065:                     $hash['pager'] = $item['value'];
2066:                 } elseif (isset($item['params']['TYPE'])) {
2067:                     if (!is_array($item['params']['TYPE'])) {
2068:                         $item['params']['TYPE'] = array($item['params']['TYPE']);
2069:                     }
2070:                     // For vCard 3.0.
2071:                     if (in_array('CELL', $item['params']['TYPE'])) {
2072:                         if (in_array('HOME', $item['params']['TYPE']) &&
2073:                             !isset($hash['homeCellPhone'])) {
2074:                             $hash['homeCellPhone'] = $item['value'];
2075:                         } elseif (in_array('WORK', $item['params']['TYPE']) &&
2076:                                   !isset($hash['workCellPhone'])) {
2077:                             $hash['workCellPhone'] = $item['value'];
2078:                         } elseif (!isset($hash['cellPhone'])) {
2079:                             $hash['cellPhone'] = $item['value'];
2080:                         }
2081:                     } elseif (in_array('FAX', $item['params']['TYPE'])) {
2082:                         if (in_array('HOME', $item['params']['TYPE']) &&
2083:                             !isset($hash['homeFax'])) {
2084:                             $hash['homeFax'] = $item['value'];
2085:                         } elseif (in_array('WORK', $item['params']['TYPE']) &&
2086:                                   !isset($hash['workFax'])) {
2087:                             $hash['workFax'] = $item['value'];
2088:                         } elseif (!isset($hash['fax'])) {
2089:                             $hash['fax'] = $item['value'];
2090:                         }
2091:                     } elseif (in_array('VIDEO', $item['params']['TYPE'])) {
2092:                         if (in_array('HOME', $item['params']['TYPE']) &&
2093:                             !isset($hash['homeVideoCall'])) {
2094:                             $hash['homeVideoCall'] = $item['value'];
2095:                         } elseif (in_array('WORK', $item['params']['TYPE']) &&
2096:                                   !isset($hash['workVideoCall'])) {
2097:                             $hash['workVideoCall'] = $item['value'];
2098:                         } elseif (!isset($hash['videoCall'])) {
2099:                             $hash['videoCall'] = $item['value'];
2100:                         }
2101:                     } elseif (in_array('PAGER', $item['params']['TYPE']) &&
2102:                               !isset($hash['pager'])) {
2103:                         $hash['pager'] = $item['value'];
2104:                     } elseif (in_array('WORK', $item['params']['TYPE']) &&
2105:                               !isset($hash['workPhone'])) {
2106:                         $hash['workPhone'] = $item['value'];
2107:                     } elseif (in_array('HOME', $item['params']['TYPE']) &&
2108:                               !isset($hash['homePhone'])) {
2109:                         $hash['homePhone'] = $item['value'];
2110:                     }
2111:                 } elseif (isset($item['params']['CELL'])) {
2112:                     if (isset($item['params']['WORK']) &&
2113:                         !isset($hash['workCellPhone'])) {
2114:                         $hash['workCellPhone'] = $item['value'];
2115:                     } elseif (isset($item['params']['HOME']) &&
2116:                               !isset($hash['homeCellPhone'])) {
2117:                         $hash['homeCellPhone'] = $item['value'];
2118:                     } elseif (!isset($hash['cellPhone'])) {
2119:                         $hash['cellPhone'] = $item['value'];
2120:                     }
2121:                 } elseif (isset($item['params']['VIDEO'])) {
2122:                     if (isset($item['params']['WORK']) &&
2123:                         !isset($hash['workVideoCall'])) {
2124:                         $hash['workVideoCall'] = $item['value'];
2125:                     } elseif (isset($item['params']['HOME']) &&
2126:                               !isset($hash['homeVideoCall'])) {
2127:                         $hash['homeVideoCall'] = $item['value'];
2128:                     } elseif (!isset($hash['homeVideoCall'])) {
2129:                         $hash['videoCall'] = $item['value'];
2130:                     }
2131:                 } elseif (count($item['params']) <= 1 ||
2132:                           isset($item['params']['VOICE'])) {
2133:                     // There might be e.g. SAT;WORK which must not overwrite
2134:                     // WORK.
2135:                     if (isset($item['params']['WORK']) &&
2136:                         !isset($hash['workPhone'])) {
2137:                         $hash['workPhone'] = $item['value'];
2138:                     } elseif (isset($item['params']['HOME']) &&
2139:                               !isset($hash['homePhone'])) {
2140:                         $hash['homePhone'] = $item['value'];
2141:                     } elseif ((count($item['params']) == 0 ||
2142:                                (count($item['params']) == 1 &&
2143:                                 isset($item['params']['VOICE']))) &&
2144:                               !isset($hash['phone'])) {
2145:                         $hash['phone'] = $item['value'];
2146:                     }
2147:                 }
2148:                 break;
2149: 
2150:             case 'EMAIL':
2151:                 $email_set = false;
2152:                 if (isset($item['params']['HOME']) &&
2153:                     (!isset($hash['homeEmail']) ||
2154:                      isset($item['params']['PREF']))) {
2155:                     $hash['homeEmail'] = Horde_Icalendar_Vcard::getBareEmail($item['value']);
2156:                     $email_set = true;
2157:                 } elseif (isset($item['params']['WORK']) &&
2158:                           (!isset($hash['workEmail']) ||
2159:                            isset($item['params']['PREF']))) {
2160:                     $hash['workEmail'] = Horde_Icalendar_Vcard::getBareEmail($item['value']);
2161:                     $email_set = true;
2162:                 } elseif (isset($item['params']['TYPE'])) {
2163:                     if (!is_array($item['params']['TYPE'])) {
2164:                         $item['params']['TYPE'] = array($item['params']['TYPE']);
2165:                     }
2166:                     if (in_array('HOME', $item['params']['TYPE']) &&
2167:                         (!isset($hash['homeEmail']) ||
2168:                          in_array('PREF', $item['params']['TYPE']))) {
2169:                         $hash['homeEmail'] = Horde_Icalendar_Vcard::getBareEmail($item['value']);
2170:                         $email_set = true;
2171:                     } elseif (in_array('WORK', $item['params']['TYPE']) &&
2172:                               (!isset($hash['workEmail']) ||
2173:                          in_array('PREF', $item['params']['TYPE']))) {
2174:                         $hash['workEmail'] = Horde_Icalendar_Vcard::getBareEmail($item['value']);
2175:                         $email_set = true;
2176:                     }
2177:                 }
2178:                 if (!$email_set &&
2179:                     (!isset($hash['email']) ||
2180:                      isset($item['params']['PREF']))) {
2181:                     $hash['email'] = Horde_Icalendar_Vcard::getBareEmail($item['value']);
2182:                 }
2183: 
2184:                 if (!isset($hash['emails'])) {
2185:                     $hash['emails'] = Horde_Icalendar_Vcard::getBareEmail($item['value']);
2186:                 } else {
2187:                     $hash['emails'] .= ', ' . Horde_Icalendar_Vcard::getBareEmail($item['value']);
2188:                 }
2189:                 break;
2190: 
2191:             case 'TITLE':
2192:                 $hash['title'] = $item['value'];
2193:                 break;
2194: 
2195:             case 'ROLE':
2196:                 $hash['role'] = $item['value'];
2197:                 break;
2198: 
2199:             case 'ORG':
2200:                 // The VCARD 2.1 specification requires the presence of two
2201:                 // SEMI-COLON separated fields: Organizational Name and
2202:                 // Organizational Unit. Additional fields are optional.
2203:                 $hash['company'] = !empty($item['values'][0]) ? $item['values'][0] : '';
2204:                 $hash['department'] = !empty($item['values'][1]) ? $item['values'][1] : '';
2205:                 break;
2206: 
2207:             case 'NOTE':
2208:                 $hash['notes'] = $item['value'];
2209:                 break;
2210: 
2211:             case 'CATEGORIES':
2212:                 $hash['businessCategory'] = $hash['category'] = str_replace('\; ', ';', $item['value']);
2213:                 break;
2214: 
2215:             case 'URL':
2216:                 if (isset($item['params']['HOME']) &&
2217:                     !isset($hash['homeWebsite'])) {
2218:                     $hash['homeWebsite'] = $item['value'];
2219:                 } elseif (isset($item['params']['WORK']) &&
2220:                           !isset($hash['workWebsite'])) {
2221:                     $hash['workWebsite'] = $item['value'];
2222:                 } elseif (!isset($hash['website'])) {
2223:                     $hash['website'] = $item['value'];
2224:                 }
2225:                 break;
2226: 
2227:             case 'BDAY':
2228:                 if (empty($item['value'])) {
2229:                     $hash['birthday'] = '';
2230:                 } else {
2231:                     $hash['birthday'] = $item['value']['year'] . '-' . $item['value']['month'] . '-' .  $item['value']['mday'];
2232:                 }
2233:                 break;
2234: 
2235:             case 'PHOTO':
2236:             case 'LOGO':
2237:                 if (isset($item['params']['VALUE']) &&
2238:                     Horde_String::lower($item['params']['VALUE']) == 'uri') {
2239:                     // No support for URIs yet.
2240:                     break;
2241:                 }
2242:                 if (!isset($item['params']['ENCODING']) ||
2243:                     (Horde_String::lower($item['params']['ENCODING']) != 'b' &&
2244:                      Horde_String::upper($item['params']['ENCODING']) != 'BASE64')) {
2245:                     // Invalid property.
2246:                     break;
2247:                 }
2248:                 $type = Horde_String::lower($item['name']);
2249:                 $hash[$type] = base64_decode($item['value']);
2250:                 if (isset($item['params']['TYPE'])) {
2251:                     $hash[$type . 'type'] = $item['params']['TYPE'];
2252:                 }
2253:                 break;
2254: 
2255:             case 'X-SIP':
2256:                 if (isset($item['params']['POC']) &&
2257:                     !isset($hash['ptt'])) {
2258:                     $hash['ptt'] = $item['value'];
2259:                 } elseif (isset($item['params']['VOIP']) &&
2260:                           !isset($hash['voip'])) {
2261:                     $hash['voip'] = $item['value'];
2262:                 } elseif (isset($item['params']['SWIS']) &&
2263:                           !isset($hash['shareView'])) {
2264:                     $hash['shareView'] = $item['value'];
2265:                 } elseif (!isset($hash['sip'])) {
2266:                     $hash['sip'] = $item['value'];
2267:                 }
2268:                 break;
2269: 
2270:             case 'X-WV-ID':
2271:                 $hash['instantMessenger'] = $item['value'];
2272:                 break;
2273: 
2274:             case 'X-ANNIVERSARY':
2275:                 if (empty($item['value'])) {
2276:                     $hash['anniversary'] = '';
2277:                 } else {
2278:                     $hash['anniversary'] = $item['value']['year'] . '-' . $item['value']['month'] . '-' . $item['value']['mday'];
2279:                 }
2280:                 break;
2281: 
2282:             case 'X-CHILDREN':
2283:                 $hash['children'] = $item['value'];
2284:                 break;
2285: 
2286:             case 'X-SPOUSE':
2287:                 $hash['spouse'] = $item['value'];
2288:                 break;
2289:             }
2290:         }
2291: 
2292:         /* Ensure we have a valid name field. */
2293:         if (empty($hash['name'])) {
2294:             /* If name is a composite field, it won't be present in the
2295:              * $this->fields array, so check for that as well. */
2296:             if (isset($this->map['name']) &&
2297:                 is_array($this->map['name']) &&
2298:                 !empty($this->map['name']['attribute'])) {
2299:                 $fieldarray = array();
2300:                 foreach ($this->map['name']['fields'] as $mapfields) {
2301:                     $fieldarray[] = isset($hash[$mapfields]) ?
2302:                         $hash[$mapfields] : '';
2303:                 }
2304:                 $hash['name'] = Turba::formatCompositeField($this->map['name']['format'], $fieldarray);
2305:             } else {
2306:                 $hash['name'] = isset($hash['firstname']) ? $hash['firstname'] : '';
2307:                 if (!empty($hash['lastname'])) {
2308:                     $hash['name'] .= ' ' . $hash['lastname'];
2309:                 }
2310:                 $hash['name'] = trim($hash['name']);
2311:             }
2312:         }
2313: 
2314:         return $hash;
2315:     }
2316: 
2317:     /**
2318:      * Convert the contact to an ActiveSync contact message
2319:      *
2320:      * @param Turba_Object $object  The turba object to convert
2321:      *
2322:      * @return Horde_ActiveSync_Message_Contact
2323:      */
2324:     public function toASContact(Turba_Object $object)
2325:     {
2326:         $message = new Horde_ActiveSync_Message_Contact(array('logger' => $GLOBALS['injector']->getInstance('Horde_Log_Logger')));
2327:         $hash = $object->getAttributes();
2328:         if (!isset($hash['lastname']) && isset($hash['name'])) {
2329:             $this->_guessName($hash);
2330:         }
2331: 
2332:         // Ensure we have at least a good guess as to separate address fields.
2333:         // Not ideal, but EAS does not have a single "address" field so we must
2334:         // map "common" to either home or work. I choose home since
2335:         // work/non-personal installs will be more likely to have separated
2336:         // address fields.
2337:         if (!empty($hash['commonAddress'])) {
2338:             if (!isset($hash['commonStreet'])) {
2339:                 $hash['commonStreet'] = $hash['commonHome'];
2340:             }
2341:             foreach (array('Address', 'Street', 'POBox', 'Extended', 'City', 'Province', 'PostalCode', 'Country') as $field) {
2342:                 $hash['home' . $field] = $hash['common' . $field];
2343:             }
2344:         } else {
2345:             if (isset($hash['homeAddress']) && !isset($hash['homeStreet'])) {
2346:                 $hash['homeStreet'] = $hash['homeAddress'];
2347:             }
2348:             if (isset($hash['workAddress']) && !isset($hash['workStreet'])) {
2349:                 $hash['workStreet'] = $hash['workAddress'];
2350:             }
2351:         }
2352: 
2353:         $haveDecodeHook = Horde::hookExists('decode_attribute', 'turba');
2354:         foreach ($hash as $field => $value) {
2355:             if ($haveDecodeHook) {
2356:                 try {
2357:                     $value = Horde::callHook(
2358:                         'decode_attribute',
2359:                         array($attribute, $this->attributes[$attribute], $this),
2360:                         'turba');
2361:                 } catch (Turba_Exception $e) {
2362:                     Horde::logMessage($e);
2363:                 }
2364:             }
2365:             switch ($field) {
2366:             case 'name':
2367:                 $message->fileas = $value;
2368:                 break;
2369: 
2370:             case 'lastname':
2371:                 $message->lastname = $value;
2372:                 break;
2373: 
2374:             case 'firstname':
2375:                 $message->firstname = $value;
2376:                 break;
2377: 
2378:             case 'middlenames':
2379:                 $message->middlename = $value;
2380:                 break;
2381: 
2382:             case 'namePrefix':
2383:                 $message->title = $value;
2384:                 break;
2385: 
2386:             case 'alias':
2387:             case 'nickname':
2388:                 try {
2389:                     $message->nickname = $value;
2390:                 } catch (InvalidArgumentException $e) {
2391:                 }
2392:                 break;
2393: 
2394:             case 'nameSuffix':
2395:                 $message->suffix = $value;
2396:                 break;
2397: 
2398:             case 'photo':
2399:                 $message->picture = base64_encode($value);
2400:                 break;
2401: 
2402:             case 'homeStreet':
2403:                 $message->homestreet = $hash['homeStreet'];
2404:                 break;
2405: 
2406:             case 'homeCity':
2407:                 $message->homecity = $hash['homeCity'];
2408:                 break;
2409: 
2410:             case 'homeProvince':
2411:                 $message->homestate = $hash['homeProvince'];
2412:                 break;
2413: 
2414:             case 'homePostalCode':
2415:                 $message->homepostalcode = $hash['homePostalCode'];
2416:                 break;
2417: 
2418:             case 'homeCountry':
2419:                 $message->homecountry = !empty($hash['homeCountry']) ? Horde_Nls::getCountryISO($hash['homeCountry']) : null;
2420:                 break;
2421: 
2422:             case 'otherStreet':
2423:                 $message->otherstreet = $hash['otherStreet'];
2424:                 break;
2425: 
2426:             case 'otherCity':
2427:                 $message->othercity = $hash['otherCity'];
2428:                 break;
2429: 
2430:             case 'otherProvince':
2431:                 $message->otherstate = $hash['otherProvince'];
2432:                 break;
2433: 
2434:             case 'otherPostalCode':
2435:                 $message->otherpostalcode = $hash['otherPostalCode'];
2436:                 break;
2437: 
2438:             case 'otherCountry':
2439:                 $message->othercountry = !empty($hash['otherCountry']) ? Horde_Nls::getCountryISO($hash['otherCountry']) : null;
2440:                 break;
2441: 
2442:             case 'workStreet':
2443:                 $message->businessstreet = $hash['workStreet'];
2444:                 break;
2445: 
2446:             case 'workCity':
2447:                 $message->businesscity = $hash['workCity'];
2448:                 break;
2449: 
2450:             case 'workProvince':
2451:                 $message->businessstate = $hash['workProvince'];
2452:                 break;
2453: 
2454:             case 'workPostalCode':
2455:                 $message->businesspostalcode = $hash['workPostalCode'];
2456:                 break;
2457: 
2458:             case 'workCountry':
2459:                 $message->businesscountry = !empty($hash['workCountry']) ? Horde_Nls::getCountryISO($hash['workCountry']) : null;
2460: 
2461:             case 'homePhone':
2462:                 /* Phone */
2463:                 $message->homephonenumber = $hash['homePhone'];
2464:                 break;
2465: 
2466:             case 'homePhone2':
2467:                 $message->home2phonenumber = $hash['homePhone2'];
2468:                 break;
2469: 
2470:             case 'cellPhone':
2471:                 $message->mobilephonenumber = $hash['cellPhone'];
2472:                 break;
2473: 
2474:             case 'carPhone':
2475:                 $message->carphonenumber = $hash['carPhone'];
2476:                 break;
2477: 
2478:             case 'fax':
2479:                 $message->businessfaxnumber = $hash['fax'];
2480:                 break;
2481: 
2482:             case 'homeFax':
2483:                 $message->homefaxnumber = $hash['homeFax'];
2484:                 break;
2485: 
2486:             case 'workPhone':
2487:                 $message->businessphonenumber = $hash['workPhone'];
2488:                 break;
2489: 
2490:             case 'workPhone2':
2491:                 $message->business2phonenumber = $hash['workPhone2'];
2492:                 break;
2493: 
2494:             case 'assistPhone':
2495:                 $message->assistnamephonenumber = $hash['assistPhone'];
2496:                 break;
2497: 
2498:             case 'companyPhone':
2499:                 $message->companymainphone = $hash['companyPhone'];
2500:                 break;
2501: 
2502:             case 'radioPhone':
2503:                 $message->radiophonenumber = $hash['radioPhone'];
2504:                 break;
2505: 
2506:             case 'pager':
2507:                 $message->pagernumber = $hash['pager'];
2508:                 break;
2509: 
2510:             case 'email':
2511:                 $message->email1address = Horde_Icalendar_Vcard::getBareEmail($value);
2512:                 break;
2513: 
2514:             case 'homeEmail':
2515:                 $message->email2address = Horde_Icalendar_Vcard::getBareEmail($value);
2516:                 break;
2517: 
2518:             case 'workEmail':
2519:                 $message->email3address = Horde_Icalendar_Vcard::getBareEmail($value);
2520:                 break;
2521: 
2522:             case 'imaddress':
2523:                 try {
2524:                     $message->imaddress = $value;
2525:                 } catch (InvalidArgumentException $e) {
2526:                 }
2527:                 break;
2528: 
2529:             case 'imaddress2':
2530:                 try {
2531:                     $message->imaddress2 = $value;
2532:                 } catch (InvalidArgumentException $e) {
2533: 
2534:                 }
2535:                 break;
2536: 
2537:             case 'imaddress3':
2538:                 try {
2539:                     $message->imaddress3 = $value;
2540:                 } catch (InvalidArgumentException $e) {
2541:                 }
2542:                 break;
2543: 
2544:             case 'title':
2545:                 $message->jobtitle = $value;
2546:                 break;
2547: 
2548:             case 'company':
2549:                 $message->companyname = $value;
2550:                 break;
2551: 
2552:             case 'department':
2553:                 $message->department = $value;
2554:                 break;
2555: 
2556:             case 'category':
2557:                 // Categories FROM horde are a simple string value, going BACK to horde are an array with 'value' and 'new' keys
2558:                 $message->categories = explode(';', $value);
2559:                 break;
2560: 
2561:             case 'spouse':
2562:                 $message->spouse = $value;
2563:                 break;
2564:             case 'notes':
2565:                 /* Assume no truncation - AS server will truncate as needed */
2566:                 $message->body = $value;
2567:                 $message->bodysize = strlen($message->body);
2568:                 $message->bodytruncated = false;
2569:                 break;
2570: 
2571:             case 'website':
2572:                 $message->webpage = $value;
2573:                 break;
2574: 
2575:             case 'birthday':
2576:             case 'anniversary':
2577:                 if (!empty($value)) {
2578:                     try {
2579:                         $date = new Horde_Date($value);
2580:                         $message->{$field} = $date;
2581:                     } catch (Horde_Date_Exception $e) {
2582:                         $message->$field = null;
2583:                     }
2584:                 } else {
2585:                     $message->$field = null;
2586:                 }
2587:                 break;
2588:             }
2589:         }
2590: 
2591:         if (empty($this->fileas)) {
2592:             $message->fileas = Turba::formatName($object);
2593:         }
2594: 
2595:         return $message;
2596:     }
2597: 
2598:     /**
2599:      * Convert an ActiveSync contact message into a hash suitable for
2600:      * importing via self::add().
2601:      *
2602:      * @param Horde_ActiveSync_Message_Contact $message  The contact message
2603:      *                                                   object.
2604:      *
2605:      * @return array  A contact hash.
2606:      */
2607:     public function fromASContact(Horde_ActiveSync_Message_Contact $message)
2608:     {
2609:         $hash = array();
2610:         $formattedname = false;
2611: 
2612:         $textMap = array(
2613:             'fileas' => 'name',
2614:             'lastname' => 'lastname',
2615:             'firstname' => 'firstname',
2616:             'middlename' => 'middlenames',
2617:             'nickname' => 'nickname',
2618:             'title' => 'namePrefix',
2619:             'suffix' => 'nameSuffix',
2620:             'homestreet' => 'homeStreet',
2621:             'homecity' => 'homeCity',
2622:             'homestate' => 'homeProvince',
2623:             'homepostalcode' => 'homePostalCode',
2624:             'otherstreet' => 'otherStreet',
2625:             'othercity' => 'otherCity',
2626:             'otherstate' => 'otherProvince',
2627:             'otherpostalcode' => 'otherPostalCode',
2628:             'businessstreet' => 'workStreet',
2629:             'businesscity' => 'workCity',
2630:             'businessstate' => 'workProvince',
2631:             'businesspostalcode' => 'workPostalCode',
2632:             'jobtitle' => 'title',
2633:             'companyname' => 'company',
2634:             'department' => 'department',
2635:             'spouse' => 'spouse',
2636:             'body' => 'notes',
2637:             'webpage' => 'website',
2638:             'assistantname' => 'assistant',
2639:             'imaddress' => 'imaddress',
2640:             'imaddress2' => 'imaddress2',
2641:             'imaddress3' => 'imaddress3'
2642:         );
2643:         foreach ($textMap as $asField => $turbaField) {
2644:             if (!$message->isGhosted($asField)) {
2645:                 try {
2646:                     $hash[$turbaField] = $message->{$asField};
2647:                 } catch (InvalidArgumentException $e) {
2648:                 }
2649:             }
2650:         }
2651: 
2652:         $nonTextMap = array(
2653:             'homephonenumber' => 'homePhone',
2654:             'home2phonenumber' => 'homePhone2',
2655:             'businessphonenumber' => 'workPhone',
2656:             'business2phonenumber' => 'workPhone2',
2657:             'businessfaxnumber' => 'fax',
2658:             'homefaxnumber' => 'homeFax',
2659:             'pagernumber' => 'pager',
2660:             'mobilephonenumber' => 'cellPhone',
2661:             'carphonenumber' => 'carPhone',
2662:             'assistnamephonenumber' => 'assistPhone',
2663:             'companymainphone' => 'companyPhone',
2664:             'radiophonenumber' => 'radioPhone'
2665:         );
2666:         foreach ($nonTextMap as $asField => $turbaField) {
2667:             if (!$message->isGhosted($asField)) {
2668:                 try {
2669:                     $hash[$turbaField] = $message->{$asField};
2670:                 } catch (InvalidArgumentException $e) {
2671:                 }
2672:             }
2673:         }
2674: 
2675:         /* Requires special handling */
2676: 
2677:         // picture ($message->picture *should* already be base64 encdoed)
2678:         if (!$message->isGhosted('picture')) {
2679:             $hash['photo'] = base64_decode($message->picture);
2680:         }
2681: 
2682:         /* Email addresses */
2683:         if (!$message->isGhosted('email1address')) {
2684:             $hash['email'] = Horde_Icalendar_Vcard::getBareEmail($message->email1address);
2685:         }
2686:         if (!$message->isGhosted('email2address')) {
2687:             $hash['homeEmail'] = Horde_Icalendar_Vcard::getBareEmail($message->email2address);
2688:         }
2689:         if (!$message->isGhosted('email3address')) {
2690:             $hash['workEmail'] = Horde_Icalendar_Vcard::getBareEmail($message->email3address);
2691:         }
2692:         /* Categories */
2693:         if (count($message->categories)) {
2694:             $hash['category'] = implode('|', $message->categories);
2695:         } elseif (!$message->isGhosted('categories')) {
2696:             $hash['category'] = '';
2697:         }
2698: 
2699:         /* Birthday and Anniversary */
2700:         if (!empty($message->birthday)) {
2701:             $bday = new Horde_Date($message->birthday);
2702:             $bday->setTimezone(date_default_timezone_get());
2703:             $hash['birthday'] = $bday->format('Y-m-d');
2704:         } elseif (!$message->isGhosted('birthday')) {
2705:             $hash['birthday'] = null;
2706:         }
2707:         if (!empty($message->anniversary)) {
2708:             $anniversary = new Horde_Date($message->anniversary);
2709:             $anniversary->setTimezone(date_default_timezone_get());
2710:             $hash['anniversary'] = $anniversary->format('Y-m-d');
2711:         } elseif (!$message->isGhosted('anniversary')) {
2712:             $hash['anniversary'] = null;
2713:         }
2714: 
2715:         /* Countries */
2716:         include 'Horde/Nls/Countries.php';
2717:         if (!empty($message->homecountry)) {
2718:             $country = array_search($message->homecountry, $countries);
2719:             if ($country === false) {
2720:                 $country = $message->homecountry;
2721:             }
2722:             $hash['homeCountry'] = $country;
2723:         } elseif (!$message->isGhosted('homecountry')) {
2724:             $hash['homeCountry'] = null;
2725:         }
2726: 
2727:         if (!empty($message->businesscountry)) {
2728:             $country = array_search($message->businesscountry, $countries);
2729:             if ($country === false) {
2730:                 $country = $message->businesscountry;
2731:             }
2732:             $hash['workCountry'] = $country;
2733:         } elseif (!$message->isGhosted('businesscountry')) {
2734:             $hash['workCountry'] = null;
2735:         }
2736: 
2737:         if (!empty($message->othercountry)) {
2738:             $country = array_search($message->othercountry, $countries);
2739:             if ($country === false) {
2740:                 $country = $message->othercountry;
2741:             }
2742:             $hash['otherCountry'] = $country;
2743:         } elseif (!$message->isGhosted('othercountry')) {
2744:             $hash['otherCountry'] = null;
2745:         }
2746: 
2747:         return $hash;
2748:     }
2749: 
2750:     /**
2751:      * Checks if the current user has the requested permissions on this
2752:      * address book.
2753:      *
2754:      * @param integer $perm  The permission to check for.
2755:      *
2756:      * @return boolean  True if the user has permission, otherwise false.
2757:      */
2758:     public function hasPermission($perm)
2759:     {
2760:         $perms = $GLOBALS['injector']->getInstance('Horde_Perms');
2761:         return $perms->exists('turba:sources:' . $this->_name)
2762:             ? $perms->hasPermission('turba:sources:' . $this->_name, $GLOBALS['registry']->getAuth(), $perm)
2763:             // Assume we have permissions if they're not explicitly set.
2764:             : true;
2765:     }
2766: 
2767:     /**
2768:      * Return the name of this address book.
2769:      * (This is the key into the cfgSources array)
2770:      *
2771:      * @string Address book name
2772:      */
2773:     public function getName()
2774:     {
2775:         return $this->_name;
2776:     }
2777: 
2778:     /**
2779:      * Return the owner to use when searching or creating contacts in
2780:      * this address book.
2781:      *
2782:      * @return string  Contact owner.
2783:      */
2784:     public function getContactOwner()
2785:     {
2786:         return empty($this->_contact_owner)
2787:             ? $this->_getContactOwner()
2788:             : $this->_contact_owner;
2789:     }
2790: 
2791:     /**
2792:      * Override the contactOwner setting for this driver.
2793:      *
2794:      * @param string $owner  The contact owner.
2795:      */
2796:     public function setContactOwner($owner)
2797:     {
2798:         $this->_contact_owner = $owner;
2799:     }
2800: 
2801:     /**
2802:      * Override the name setting for this driver.
2803:      *
2804:      * @param string $name  The source name. This is the key into the
2805:      *                      $cfgSources array.
2806:      */
2807:     public function setSourceName($name)
2808:     {
2809:         $this->_name = $name;
2810:     }
2811: 
2812:     /**
2813:      * Return the owner to use when searching or creating contacts in
2814:      * this address book.
2815:      *
2816:      * @return string  Contact owner.
2817:      */
2818:     protected function _getContactOwner()
2819:     {
2820:         return $GLOBALS['registry']->getAuth();
2821:     }
2822: 
2823:     /**
2824:      * Creates a new Horde_Share for this source type.
2825:      *
2826:      * @param string $share_name  The share name
2827:      * @param array  $params      The params for the share.
2828:      *
2829:      * @return Horde_Share  The share object.
2830:      */
2831:     public function createShare($share_name, array $params)
2832:     {
2833:         // If the raw address book name is not set, use the share name
2834:         if (empty($params['params']['name'])) {
2835:             $params['params']['name'] = $share_name;
2836:         }
2837: 
2838:         return Turba::createShare($share_name, $params);
2839:     }
2840: 
2841:     /**
2842:      * Creates an object key for a new object.
2843:      *
2844:      * @param array $attributes  The attributes (in driver keys) of the
2845:      *                           object being added.
2846:      *
2847:      * @return string  A unique ID for the new object.
2848:      */
2849:     protected function _makeKey(array $attributes)
2850:     {
2851:         return hash('md5', mt_rand());
2852:     }
2853: 
2854:     /**
2855:      * Creates an object UID for a new object.
2856:      *
2857:      * @return string  A unique ID for the new object.
2858:      */
2859:     protected function _makeUid()
2860:     {
2861:         return strval(new Horde_Support_Guid());
2862:     }
2863: 
2864:     /**
2865:      * Initialize the driver.
2866:      *
2867:      * @throws Turba_Exception
2868:      */
2869:     protected function _init()
2870:     {
2871:     }
2872: 
2873:     /**
2874:      * Searches the address book with the given criteria and returns a
2875:      * filtered list of results. If the criteria parameter is an empty array,
2876:      * all records will be returned.
2877:      *
2878:      * @param array $criteria       Array containing the search criteria.
2879:      * @param array $fields         List of fields to return.
2880:      * @param array $blobFields     Array of fields containing binary data.
2881:      * @param boolean $count_only   Only return the count of matching entries,
2882:      *                              not the entries themselves.
2883:      *
2884:      * @return array  Hash containing the search results.
2885:      * @throws Turba_Exception
2886:      */
2887:     protected function _search(array $criteria, array $fields, array $blobFields = array(), $count_only = false)
2888:     {
2889:         throw new Turba_Exception(_("Searching is not available."));
2890:     }
2891: 
2892:     /**
2893:      * Reads the given data from the address book and returns the results.
2894:      *
2895:      * @param string $key        The primary key field to use.
2896:      * @param mixed $ids         The ids of the contacts to load.
2897:      * @param string $owner      Only return contacts owned by this user.
2898:      * @param array $fields      List of fields to return.
2899:      * @param array $blobFields  Array of fields containing binary data.
2900:      *
2901:      * @return array  Hash containing the search results.
2902:      * @throws Turba_Exception
2903:      */
2904:     protected function _read($key, $ids, $owner, array $fields,
2905:                              array $blobFields = array())
2906:     {
2907:         throw new Turba_Exception(_("Reading contacts is not available."));
2908:     }
2909: 
2910:     /**
2911:      * Adds the specified contact to the addressbook.
2912:      *
2913:      * @param array $attributes  The attribute values of the contact.
2914:      * @param array $blob_fields TODO
2915:      *
2916:      * @throws Turba_Exception
2917:      */
2918:     protected function _add(array $attributes, array $blob_fields = array())
2919:     {
2920:         throw new Turba_Exception(_("Adding contacts is not available."));
2921:     }
2922: 
2923:     /**
2924:      * Deletes the specified contact from the addressbook.
2925:      *
2926:      * @param string $object_key TODO
2927:      * @param string $object_id  TODO
2928:      *
2929:      * @throws Turba_Exception
2930:      */
2931:     protected function _delete($object_key, $object_id)
2932:     {
2933:         throw new Turba_Exception(_("Deleting contacts is not available."));
2934:     }
2935: 
2936:     /**
2937:      * Saves the specified object in the SQL database.
2938:      *
2939:      * @param Turba_Object $object  The object to save
2940:      *
2941:      * @return string  The object id, possibly updated.
2942:      * @throws Turba_Exception
2943:      */
2944:     protected function _save(Turba_Object $object)
2945:     {
2946:         throw new Turba_Exception(_("Saving contacts is not available."));
2947:     }
2948: 
2949:     /**
2950:      * Remove all entries owned by the specified user.
2951:      *
2952:      * @param string $user  The user's data to remove.
2953:      *
2954:      * @throws Turba_Exception
2955:      */
2956:     public function removeUserData($user)
2957:     {
2958:         throw new Turba_Exception_NotSupported(_("Removing user data is not supported in the current address book storage driver."));
2959:     }
2960: 
2961:     /**
2962:      * Check if the passed in share is the default share for this source.
2963:      *
2964:      * @param Horde_Share_Object $share  The share object.
2965:      * @param array $srcconfig           The cfgSource entry for the share.
2966:      *
2967:      * @return boolean TODO
2968:      */
2969:     public function checkDefaultShare(Horde_Share_Object $share, array $srcconfig)
2970:     {
2971:         $params = @unserialize($share->get('params'));
2972:         if (!isset($params['default'])) {
2973:             $params['default'] = ($params['name'] == $GLOBALS['registry']->getAuth());
2974:             $share->set('params', serialize($params));
2975:             $share->save();
2976:         }
2977: 
2978:         return $params['default'];
2979:     }
2980: 
2981:     /* Countable methods. */
2982: 
2983:     /**
2984:      * Returns the number of contacts of the current user in this address book.
2985:      *
2986:      * @return integer  The number of contacts that the user owns.
2987:      * @throws Turba_Exception
2988:      */
2989:     public function count()
2990:     {
2991:         if (is_null($this->_count)) {
2992:             $this->_count = count(
2993:                 $this->_search(array('AND' => array(
2994:                                    array('field' => $this->toDriver('__owner'),
2995:                                          'op' => '=',
2996:                                          'test' => $this->getContactOwner()))),
2997:                                array($this->toDriver('__key')))
2998:             );
2999:         }
3000: 
3001:         return $this->_count;
3002:     }
3003: 
3004:     /**
3005:      * Helper function for guessing name parts from a single name string.
3006:      *
3007:      * @param array $hash  The attributes array.
3008:      */
3009:     protected function _guessName(&$hash)
3010:     {
3011:         if (($pos = strpos($hash['name'], ',')) !== false) {
3012:             // Assume Last, First
3013:             $hash['lastname'] = Horde_String::substr($hash['name'], 0, $pos);
3014:             $hash['firstname'] = trim(Horde_String::substr($hash['name'], $pos + 1));
3015:         } elseif (($pos = Horde_String::rpos($hash['name'], ' ')) !== false) {
3016:             // Assume everything after last space as lastname
3017:             $hash['lastname'] = trim(Horde_String::substr($hash['name'], $pos + 1));
3018:             $hash['firstname'] = Horde_String::substr($hash['name'], 0, $pos);
3019:         } else {
3020:             $hash['lastname'] = $hash['name'];
3021:             $hash['firstname'] = '';
3022:         }
3023:     }
3024: 
3025: }
3026: 
API documentation generated by ApiGen