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:  * Turba external API interface.
   4:  *
   5:  * This file defines Turba's external API interface. Other applications can
   6:  * interact with Turba through this API.
   7:  *
   8:  * Copyright 2009-2012 Horde LLC (http://www.horde.org/)
   9:  *
  10:  * See the enclosed file LICENSE for license information (ASL).  If you did
  11:  * did not receive this file, see http://www.horde.org/licenses/apache.
  12:  *
  13:  * @author   Michael Slusarz <slusarz@horde.org>
  14:  * @category Horde
  15:  * @license  http://www.horde.org/licenses/apache ASL
  16:  * @package  Turba
  17:  */
  18: class Turba_Api extends Horde_Registry_Api
  19: {
  20:     /**
  21:      * Links.
  22:      *
  23:      * @var array
  24:      */
  25:     public $links = array(
  26:         'show' => '%application%/contact.php?source=|source|&key=|key|&uid=|uid|'
  27:     );
  28: 
  29:     /**
  30:      * The listing of API calls that do not require permissions checking.
  31:      *
  32:      * @var array
  33:      */
  34:     public $noPerms = array(
  35:         'getClientSource', 'getClient', 'getClients', 'searchClients'
  36:     );
  37: 
  38:     /**
  39:      * Callback for comment API.
  40:      *
  41:      * @param integer $id  Internal data identifier.
  42:      *
  43:      * @return mixed  Name of object on success, false on failure.
  44:      */
  45:     public function commentCallback($id)
  46:     {
  47:         if (!$GLOBALS['conf']['comments']['allow']) {
  48:             return false;
  49:         }
  50: 
  51:         @list($source, $key) = explode('.', $id, 2);
  52:         if (isset($GLOBALS['cfgSources'][$source]) && $key) {
  53:             try {
  54:                 return $GLOBALS['injector']->getInstance('Turba_Factory_Driver')->create($source)->getObject($key)->getValue('name');
  55:             } catch (Turba_Exception $e) {}
  56:         }
  57: 
  58:         return false;
  59:     }
  60: 
  61:     /**
  62:      * Returns if applications allows comments
  63:      *
  64:      * @return boolean
  65:      */
  66:     public function hasComments()
  67:     {
  68:         return $GLOBALS['conf']['comments']['allow'];
  69:     }
  70: 
  71:     /**
  72:      * Returns a list of available sources.
  73:      *
  74:      * @param boolean $writeable  Set to true to limit to writeable sources.
  75:      *
  76:      * @return array  An array of the available sources.
  77:      */
  78:     public function sources($writeable = false)
  79:     {
  80:         $addressbooks = Turba::getAddressBooks($writeable ? Horde_Perms::EDIT : Horde_Perms::READ);
  81:         foreach ($addressbooks as $addressbook => $config) {
  82:             $addressbooks[$addressbook] = $config['title'];
  83:         }
  84: 
  85:         return $addressbooks;
  86:     }
  87: 
  88:     /**
  89:      * Returns a list of fields avaiable in a source.
  90:      *
  91:      * @param string $source  The name of the source
  92:      *
  93:      * @return array  An array describing the fields.
  94:      * @throws Turba_Exception
  95:      */
  96:     public function fields($source = null)
  97:     {
  98:         global $cfgSources, $attributes;
  99: 
 100:         if (empty($source) || !isset($cfgSources[$source])) {
 101:             throw new Turba_Exception(sprintf(_("Invalid address book: %s"), $source));
 102:         }
 103: 
 104:         $fields = array();
 105:         foreach ($cfgSources[$source]['map'] as $field_name => $null) {
 106:             if (substr($field_name, 0, 2) != '__') {
 107:                 $fields[$field_name] = array('name' => $field_name,
 108:                     'type' => $attributes[$field_name]['type'],
 109:                     'label' => $attributes[$field_name]['label'],
 110:                     'search' => in_array($field_name, $cfgSources[$source]['search']));
 111:             }
 112:         }
 113: 
 114:         return $fields;
 115:     }
 116: 
 117:     /**
 118:      * Retrieve the UID for the current user's default Turba share.
 119:      */
 120:     public function getDefaultShare()
 121:     {
 122:         global $prefs, $session;
 123: 
 124:         // Bring in a clean copy of sources.
 125:         $cfgSources = Turba::availableSources();
 126: 
 127:         if ($session->get('turba', 'has_share')) {
 128:             $shares = Turba::listShares(true);
 129:             foreach ($shares as $uid => $share) {
 130:                 $params = @unserialize($share->get('params'));
 131:                 if (empty($params['source'])) {
 132:                     continue;
 133:                 }
 134: 
 135:                 try {
 136:                     $driver = $GLOBALS['injector']->getInstance('Turba_Factory_Driver')->create($uid);
 137:                     if ($driver->checkDefaultShare($share, $cfgSources[$params['source']])) {
 138:                         return $uid;
 139:                     }
 140:                 } catch (Turba_Exception $e) {}
 141:             }
 142:         }
 143: 
 144:         // Return Turba's default_dir as default
 145:         return $prefs->getValue('default_dir');
 146:     }
 147: 
 148:     /**
 149:      * Retrieve the UID for the Global Address List source, or false if none
 150:      * configured.
 151:      *
 152:      * @return string | boolean  The UID or false if none configured.
 153:      */
 154:     public function getGalUid()
 155:     {
 156:         if (!empty($GLOBALS['conf']['gal']['addressbook'])) {
 157:             return $GLOBALS['conf']['gal']['addressbook'];
 158:         }
 159: 
 160:         return false;
 161:     }
 162: 
 163:     private function _modified($uid)
 164:     {
 165:         $modified = $this->getActionTimestamp($uid, 'modify');
 166:         if (empty($modified)) {
 167:             $modified = $this->getActionTimestamp($uid, 'add');
 168:         }
 169:         return $modified;
 170:     }
 171: 
 172:     /**
 173:      * Browses through Turba's object tree.
 174:      *
 175:      * @param string $path       The path of the tree to browse.
 176:      * @param array $properties  The item properties to return. Defaults to
 177:      *                           'name', 'icon', and 'browseable'.
 178:      *
 179:      * @return array  Content of the specified path.
 180:      * @throws Turba_Exception
 181:      */
 182:     public function browse($path = '', $properties = array())
 183:     {
 184:         global $registry, $session, $cfgSources;
 185: 
 186:         // Default properties.
 187:         if (!$properties) {
 188:             $properties = array('name', 'icon', 'browseable');
 189:         }
 190: 
 191:         // Strip off the application name if present
 192:         if (substr($path, 0, 5) == 'turba') {
 193:             $path = substr($path, 5);
 194:         }
 195:         $path = trim($path, '/');
 196:         $parts = explode('/', $path);
 197: 
 198:         $now = time();
 199:         $results = array();
 200:         if (empty($path)) {
 201:             // We always provide the "global" folder which contains address book
 202:             // sources that are shared among all users.  Per-user shares are shown
 203:             // in a folder for each respective user.
 204:             $results = array();
 205:             $shares = Turba::listShares();
 206:             $owners = array('global' => true);
 207:             foreach ($shares as $share) {
 208:                 $owners[$share->get('owner') ? $share->get('owner') : '-system-'] = true;
 209:             }
 210: 
 211:             foreach (array_keys($owners) as $owner) {
 212:                 if (in_array('name', $properties)) {
 213:                     $results['turba/' . $owner]['name'] = $owner;
 214:                 }
 215:                 if (in_array('icon', $properties)) {
 216:                     $results['turba/' . $owner]['icon'] = Horde_Themes::img('turba.png');
 217:                 }
 218:                 if (in_array('browseable', $properties)) {
 219:                     $results['turba/' . $owner]['browseable'] = true;
 220:                 }
 221:                 if (in_array('contenttype', $properties)) {
 222:                     $results['turba/' . $owner]['contenttype'] = 'httpd/unix-directory';
 223:                 }
 224:                 if (in_array('contentlength', $properties)) {
 225:                     $results['turba/' . $owner]['contentlength'] = 0;
 226:                 }
 227:                 if (in_array('modified', $properties)) {
 228:                     // @TODO: Get a real modification date
 229:                     $results['turba/' . $owner]['modified'] = $now;
 230:                 }
 231:                 if (in_array('created', $properties)) {
 232:                     // @TODO Get a real creation date
 233:                     $results['turba/' . $owner]['created'] = 0;
 234:                 }
 235:             }
 236:             return $results;
 237:         } elseif (count($parts) == 1) {
 238:             //
 239:             // We should either have the username that is a valid share owner or
 240:             // 'global'
 241:             //
 242:             if (empty($parts[0])) {
 243:                 // We need either 'global' or a valid username with shares
 244:                 return array();
 245:             }
 246: 
 247:             if ($parts[0] == 'global') {
 248:                 // The client is requesting a list of global address books.
 249:                 $addressbooks = Turba::getAddressBooks();
 250:                 foreach ($addressbooks as $addressbook => $info) {
 251:                     if ($info['type'] == 'share') {
 252:                         // Ignore address book shares in the 'global' folder
 253:                         unset($addressbooks[$addressbook]);
 254:                     }
 255:                 }
 256:             } else {
 257:                 // Assume $parts[0] is a valid username and we need to list their
 258:                 // shared addressbooks.
 259:                 if (!$session->get('turba', 'has_share')) {
 260:                     // No backends are configured to provide shares
 261:                     return array();
 262:                 }
 263:                 $addressbooks = $GLOBALS['turba_shares']->listShares(
 264:                     $parts[0],
 265:                     array('perm' => Horde_Perms::READ,
 266:                           'attributes' => $parts[0]));
 267:                 // The last check returns all addressbooks for the requested user,
 268:                 // but that does not mean the requesting user has access to them.
 269:                 // Filter out those address books for which the requesting user has
 270:                 // no access.
 271:                 $addressbooks = Turba::permissionsFilter($addressbooks);
 272:             }
 273: 
 274:             $curpath = 'turba/' . $parts[0] . '/';
 275:             foreach ($addressbooks as $addressbook => $info) {
 276:                 if (in_array('name', $properties)) {
 277:                     if ($info instanceof Horde_Share_Object) {
 278:                         $name = $info->get('title');
 279:                     } else {
 280:                         $name = $info['title'];
 281:                     }
 282:                     $results[$curpath . $addressbook]['name'] = $name;
 283:                 }
 284:                 if (in_array('icon', $properties)) {
 285:                     $results[$curpath . $addressbook]['icon'] = Horde_Themes::img('turba.png');
 286:                 }
 287:                 if (in_array('browseable', $properties)) {
 288:                     $results[$curpath . $addressbook]['browseable'] = true;
 289:                 }
 290:                 if (in_array('contenttype', $properties)) {
 291:                     $results[$curpath . $addressbook]['contenttype'] = 'httpd/unix-directory';
 292:                 }
 293:                 if (in_array('contentlength', $properties)) {
 294:                     $results[$curpath . $addressbook]['contentlength'] = 0;
 295:                 }
 296:                 if (in_array('modified', $properties)) {
 297:                     // @TODO: Get a real modification date
 298:                     $results[$curpath . $addressbook]['modified'] = $now;
 299:                 }
 300:                 if (in_array('created', $properties)) {
 301:                     // @TODO Get a real creation date
 302:                     $results[$curpath . $addressbook]['created'] = 0;
 303:                 }
 304:             }
 305:             return $results;
 306: 
 307:         } elseif (count($parts) == 2) {
 308:             //
 309:             // The client is requesting all contacts from a given addressbook
 310:             //
 311:             if (empty($parts[0]) || empty($parts[1])) {
 312:                 // $parts[0] must be either 'global' or a valid user with shares
 313:                 // $parts[1] must be an address book ID
 314:                 return array();
 315:             }
 316: 
 317:             $addressbooks = Turba::getAddressBooks();
 318:             if (!isset($addressbooks[$parts[1]])) {
 319:                 // We must have a valid addressbook to continue.
 320:                 return array();
 321:             }
 322: 
 323:             // Load the Turba driver.
 324:             $driver = $GLOBALS['injector']->getInstance('Turba_Factory_Driver')->create($parts[1]);
 325: 
 326:             $contacts = $driver->search(array());
 327: 
 328:             $contacts->reset();
 329:             $curpath = 'turba/' . $parts[0] . '/' . $parts[1] . '/';
 330:             while ($contact = $contacts->next()) {
 331:                 $key = $curpath . $contact->getValue('__key');
 332:                 if (in_array('name', $properties)) {
 333:                     $results[$key]['name'] = Turba::formatName($contact);
 334:                 }
 335:                 if (in_array('icon', $properties)) {
 336:                     $results[$key]['icon'] = Horde_Themes::img('mime/vcard.png');
 337:                 }
 338:                 if (in_array('browseable', $properties)) {
 339:                     $results[$key]['browseable'] = false;
 340:                 }
 341:                 if (in_array('contenttype', $properties)) {
 342:                     $results[$key]['contenttype'] = 'text/x-vcard';
 343:                 }
 344:                 if (in_array('contentlength', $properties)) {
 345:                     try {
 346:                         $data = $this->export($contact->getValue('__uid'), 'text/x-vcard', $contact->getSource());
 347:                     } catch (Turba_Exception $e) {
 348:                         $data = '';
 349:                     }
 350:                     $results[$key]['contentlength'] = strlen($data);
 351:                 }
 352:                 if (in_array('modified', $properties)) {
 353:                     $results[$key]['modified'] = $this->_modified($contact->getValue('__uid'));
 354:                 }
 355:                 if (in_array('created', $properties)) {
 356:                     $results[$key]['created'] = $this->getActionTimestamp($contact->getValue('__uid'), 'add');
 357:                 }
 358:             }
 359: 
 360:             return $results;
 361: 
 362:         } elseif (count($parts) == 3) {
 363:             //
 364:             // The client is requesting an individual contact
 365:             //
 366:             $addressbooks = Turba::getAddressBooks();
 367:             if (!isset($addressbooks[$parts[1]])) {
 368:                 // We must have a valid addressbook to continue.
 369:                 return array();
 370:             }
 371: 
 372:             // Load the Turba driver.
 373:             $driver = $GLOBALS['injector']->getInstance('Turba_Factory_Driver')->create($parts[1]);
 374: 
 375:             $contact = $driver->getObject($parts[2]);
 376: 
 377:             $result = array('data' => $this->export($contact->getValue('__uid'), 'text/x-vcard', $contact->getSource()),
 378:                 'mimetype' => 'text/x-vcard');
 379:             $modified = $this->_modified($contact->getValue('__uid'));
 380:             if (!empty($modified)) {
 381:                 $result['mtime'] = $modified;
 382:             }
 383:             return $result;
 384:         } else {
 385:             throw new Turba_Exception(_("Malformed request."));
 386:         }
 387:     }
 388: 
 389:     /**
 390:      * Deletes a file from the Turba tree.
 391:      *
 392:      * @param string $path  The path to the file.
 393:      *
 394:      * @return string  The event's UID.
 395:      * @throws Turba_Exception
 396:      */
 397:     public function path_delete($path)
 398:     {
 399:         global $registry, $cfgSources;
 400: 
 401:         // Strip off the application name if present
 402:         if (substr($path, 0, 5) == 'turba') {
 403:             $path = substr($path, 5);
 404:         }
 405:         $path = trim($path, '/');
 406:         $parts = explode('/', $path);
 407: 
 408:         $now = time();
 409:         $results = array();
 410: 
 411:         if (count($parts) < 3) {
 412:             // Deletes must be on individual contacts
 413:             throw new Turba_Exception(_("Delete denied."));
 414:         }
 415:         if (!array_key_exists($parts[1], Turba::getAddressBooks())) {
 416:             throw new Turba_Exception("Address book does not exist");
 417:         }
 418: 
 419:         // Load the Turba driver.
 420:         $driver = $GLOBALS['injector']->getInstance('Turba_Factory_Driver')->create($parts[1]);
 421: 
 422:         return $driver->delete($parts[2]);
 423:     }
 424: 
 425:     /**
 426:      * Returns an array of UIDs for all contacts that the current user is
 427:      * authorized to see.
 428:      *
 429:      * @param string|array $sources  The name(s) of the source(s) to return
 430:      *                               contacts of. If left empty, the current
 431:      *                               user's sync sources or default source are
 432:      *                               used.
 433:      *
 434:      * @return array  An array of UIDs for all contacts the user can access.
 435:      * @throws Turba_Exception
 436:      */
 437:     public function listUids($sources = null)
 438:     {
 439:         global $cfgSources, $prefs;
 440: 
 441:         /* Get default address book from user preferences. */
 442:         if (empty($sources)) {
 443:             $sources = @unserialize($prefs->getValue('sync_books'));
 444:         } elseif (!is_array($sources)) {
 445:             $sources = array($sources);
 446:         }
 447:         if (empty($sources)) {
 448:             $sources = array(Turba::getDefaultAddressbook());
 449:         }
 450:         if (empty($sources)) {
 451:             throw new Turba_Exception(_("No address book specified"));
 452:         }
 453: 
 454:         $uids = array();
 455:         foreach ($sources as $source) {
 456:             if (empty($source) || !isset($cfgSources[$source])) {
 457:                 throw new Turba_Exception(sprintf(_("Invalid address book: %s"), $source));
 458:             }
 459: 
 460:             $storage = $GLOBALS['injector']->getInstance('Turba_Factory_Driver')->create($source);
 461: 
 462:             try {
 463:                 $results = $storage->search(array());
 464:             } catch (Turba_Exception $e) {
 465:                 throw new Turba_Exception(sprintf(_("Error searching the address book: %s"), $e->getMessage()));
 466:             }
 467: 
 468:             foreach ($results->objects as $o) {
 469:                 if (!$o->isGroup()) {
 470:                     $uids[] = $o->getValue('__uid');
 471:                 }
 472:             }
 473:         }
 474: 
 475:         return $uids;
 476:     }
 477: 
 478:     /**
 479:      * Returns an array of UIDs for contacts that have had $action happen since
 480:      * $timestamp.
 481:      *
 482:      * @param string  $action        The action to check for - add, modify, or
 483:      *                               delete.
 484:      * @param integer $timestamp     The time to start the search.
 485:      * @param string|array $sources  The source(s) for which to retrieve the
 486:      *                               history.
 487:      * @param integer $end           The optinal ending timestamp.
 488:      *
 489:      * @return array  An array of UIDs matching the action and time criteria.
 490:      *
 491:      * @throws Turba_Exception
 492:      * @throws InvalidArgumentException
 493:      */
 494:     public function listBy($action, $timestamp, $sources = null, $end = null)
 495:     {
 496:         global $prefs, $cfgSources;
 497: 
 498:         /* Get default address book from user preferences. */
 499:         if (empty($sources)) {
 500:             $sources = @unserialize($prefs->getValue('sync_books'));
 501:         } elseif (!is_array($sources)) {
 502:             $sources = array($sources);
 503:         }
 504:         if (empty($sources)) {
 505:             $sources = array(Turba::getDefaultAddressbook());
 506:         }
 507: 
 508:         if (empty($sources)) {
 509:             throw new Turba_Exception(_("No address book specified"));
 510:         }
 511: 
 512:         $uids = array();
 513:         $history = $GLOBALS['injector']->getInstance('Horde_History');
 514:         $filter = array(array('op' => '=', 'field' => 'action', 'value' => $action));
 515:         if (!empty($end)) {
 516:             $filter[] = array('op' => '<', 'field' => 'ts', 'value' => $end);
 517:         }
 518: 
 519:         foreach ($sources as $source) {
 520:             if (empty($source) || !isset($cfgSources[$source])) {
 521:                 throw new Turba_Exception(sprintf(_("Invalid address book: %s"), $source));
 522:             }
 523: 
 524:             $driver = $GLOBALS['injector']->getInstance('Turba_Factory_Driver')->create($source);
 525: 
 526:             $histories = $history->getByTimestamp(
 527:                 '>', $timestamp, $filter,
 528:                 'turba:' . $driver->getName());
 529: 
 530:             // Filter out groups
 531:             $nguids = str_replace(
 532:                 'turba:' . $driver->getName() . ':',
 533:                 '',
 534:                 array_keys($histories));
 535: 
 536:             $include = array();
 537:             foreach ($nguids as $uid) {
 538:                 if ($action != 'delete') {
 539:                     $list = $driver->search(array('__uid' => $uid));
 540:                     if ($list->count()) {
 541:                         $object = $list->next();
 542:                         if ($object->isGroup()) {
 543:                             continue;
 544:                         }
 545:                     }
 546:                 }
 547:                 $include[] = $uid;
 548:             }
 549: 
 550:             // Strip leading turba:addressbook:.
 551:             $uids = array_merge($uids, $include);
 552:         }
 553: 
 554:         return $uids;
 555:     }
 556: 
 557:     /**
 558:      * Method for obtaining all server changes between two timestamps. Basically
 559:      * a wrapper around listBy(), but returns an array containing all adds,
 560:      * edits and deletions.
 561:      *
 562:      * @param integer $start             The starting timestamp
 563:      * @param integer $end               The ending timestamp.
 564:      *
 565:      * @return array  An hash with 'add', 'modify' and 'delete' arrays.
 566:      */
 567:     public function getChanges($start, $end)
 568:     {
 569:         return array('add' => $this->listBy('add', $start, null, $end),
 570:                      'modify' => $this->listBy('modify', $start, null, $end),
 571:                      'delete' => $this->listBy('delete', $start, null, $end));
 572:     }
 573: 
 574:     /**
 575:      * Returns the timestamp of an operation for a given uid an action.
 576:      *
 577:      * @param string $uid            The uid to look for.
 578:      * @param string $action         The action to check for - add, modify, or
 579:      *                               delete.
 580:      * @param string|array $sources  The source(s) for which to retrieve the
 581:      *                               history.
 582:      *
 583:      * @return integer  The timestamp for this action.
 584:      *
 585:      * @throws Turba_Exception
 586:      * @throws InvalidArgumentException
 587:      */
 588:     public function getActionTimestamp($uid, $action, $sources = null)
 589:     {
 590:         global $prefs, $cfgSources;
 591: 
 592:         /* Get default address book from user preferences. */
 593:         if (empty($sources)) {
 594:             $sources = @unserialize($prefs->getValue('sync_books'));
 595:         } elseif (!is_array($sources)) {
 596:             $sources = array($sources);
 597:         }
 598:         if (empty($sources)) {
 599:             $sources = array(Turba::getDefaultAddressbook());
 600:         }
 601:         if (empty($sources)) {
 602:             throw new Turba_Exception(_("No address book specified"));
 603:         }
 604: 
 605:         $last = 0;
 606:         $history = $GLOBALS['injector']->getInstance('Horde_History');
 607:         foreach ($sources as $source) {
 608:             if (empty($source) || !isset($cfgSources[$source])) {
 609:                 throw new Turba_Exception(sprintf(_("Invalid address book: %s"), $source));
 610:             }
 611: 
 612:             $driver = $GLOBALS['injector']->getInstance('Turba_Factory_Driver')->create($source);
 613: 
 614:             $ts = $history->getActionTimestamp('turba:' . $driver->getName()
 615:                 . ':' . $uid,
 616:                 $action);
 617:             if (!empty($ts) && $ts > $last) {
 618:                 $last = $ts;
 619:             }
 620:         }
 621: 
 622:         return $last;
 623:     }
 624: 
 625:     /**
 626:      * Import a contact represented in the specified contentType.
 627:      *
 628:      * @param string $content      The content of the contact.
 629:      * @param string $contentType  What format is the data in? Currently
 630:      *                             supports array, text/directory, text/vcard,
 631:      *                             text/x-vcard, and activesync.
 632:      * @param string $source       The source into which the contact will be
 633:      *                             imported.
 634:      *
 635:      * @return string  The new UID, or false on failure.
 636:      * @throws Turba_Exception
 637:      */
 638:     public function import($content, $contentType = 'array',
 639:                            $import_source = null)
 640:     {
 641:         global $cfgSources, $prefs;
 642: 
 643:         /* Get default address book from user preferences. */
 644:         if (empty($import_source)) {
 645:             $import_source = $prefs->getValue('default_dir');
 646:             // On new installations default_dir is not set, use first source
 647:             // instead.
 648:             if (empty($import_source)) {
 649:                 $import_source = key(Turba::getAddressBooks(Horde_Perms::EDIT));
 650:             }
 651:         }
 652: 
 653:         // Check existance of and permissions on the specified source.
 654:         if (!isset($cfgSources[$import_source])) {
 655:             throw new Turba_Exception(
 656:                 sprintf(_("Invalid address book: %s"), $import_source));
 657:         }
 658: 
 659:         $driver = $GLOBALS['injector']
 660:             ->getInstance('Turba_Factory_Driver')
 661:             ->create($import_source);
 662: 
 663:         if (!$driver->hasPermission(Horde_Perms::EDIT)) {
 664:             throw new Turba_Exception(_("Permission denied"));
 665:         }
 666: 
 667:         // Create a category manager.
 668:         $cManager = new Horde_Prefs_CategoryManager();
 669:         $categories = $cManager->get();
 670: 
 671:         // Need an object to add attributes to.
 672:         $object = new Turba_Object($driver);
 673: 
 674:         if (!($content instanceof Horde_Icalendar_Vcard)) {
 675:             switch ($contentType) {
 676:             case 'array':
 677:                 break;
 678: 
 679:             case 'text/x-vcard':
 680:             case 'text/vcard':
 681:             case 'text/directory':
 682:                 $iCal = new Horde_Icalendar();
 683:                 if (!$iCal->parsevCalendar($content)) {
 684:                     throw new Turba_Exception(_("There was an error importing the iCalendar data."));
 685:                 }
 686:                 switch ($iCal->getComponentCount()) {
 687:                 case 0:
 688:                     throw new Turba_Exception(_("No vCard data was found."));
 689: 
 690:                 case 1:
 691:                     $content = $iCal->getComponent(0);
 692:                     break;
 693: 
 694:                 default:
 695:                     $ids = array();
 696:                     foreach ($iCal->getComponents() as $c) {
 697:                         if ($c instanceof Horde_Icalendar_Vcard) {
 698:                             $content = $driver->toHash($c);
 699:                             $result = $driver->search($content);
 700:                             if (count($result)) {
 701:                                 continue;
 702:                             }
 703: 
 704:                             $result = $driver->add($content);
 705:                             if (!empty($content['category']) &&
 706:                                 !in_array($content['category'], $categories)) {
 707:                                     $cManager->add($content['category']);
 708:                                     $categories[] = $content['category'];
 709:                             }
 710:                             $ids[] = $result;
 711:                         }
 712:                     }
 713: 
 714:                     return $ids;
 715:                 }
 716:                 break;
 717: 
 718:             case 'activesync':
 719:                 $content = $driver->fromASContact($content);
 720:                 break;
 721: 
 722:             default:
 723:                 throw new Turba_Exception(sprintf(_("Unsupported Content-Type: %s"), $contentType));
 724:             }
 725:         }
 726: 
 727:         if ($content instanceof Horde_Icalendar_Vcard) {
 728:             $content = $driver->toHash($content);
 729:         }
 730: 
 731:         // Check if the entry already exists in the data source:
 732:         $result = $driver->search($content);
 733:         if (count($result)) {
 734:             $o = $result->objects[0];
 735:             throw new Turba_Exception(_("Already Exists"));
 736:         }
 737: 
 738:         // We can't use $object->setValue() here since that cannot be used with
 739:         // composite fields.
 740:         if (Horde::hookExists('encode_attribute', 'turba')) {
 741:             foreach ($content as $attribute => &$value) {
 742:                 try {
 743:                     $value = Horde::callHook('encode_attribute', array($attribute, $value, null, null), 'turba');
 744:                 } catch (Turba_Exception $e) {}
 745:             }
 746:         }
 747:         $result = $driver->add($content);
 748: 
 749:         if (!empty($content['category']) &&
 750:             !in_array($content['category'], $categories)) {
 751:                 $cManager->add($content['category']);
 752:             }
 753: 
 754:         return $driver->getObject($result)->getValue('__uid');
 755:     }
 756: 
 757:     /**
 758:      * Export a contact, identified by UID, in the requested contentType.
 759:      *
 760:      * @param string $uid            Identify the contact to export.
 761:      * @param mixed $contentType     What format should the data be in?  Either
 762:      *                               a string with one of: - text/directory -
 763:      *                               text/vcard - text/x-vcard The first two
 764:      *                               produce a vcard3.0 (rfc2426), the second
 765:      *                               produces a vcard in old 2.1 format
 766:      *                               defined by imc.org Also supports a raw
 767:      *                               array
 768:      * @param string|array $sources The source(s) from which the contact will
 769:      *                               be exported.
 770:      * @param array $fields          Hash of field names and
 771:      *                               Horde_SyncMl_Property properties with the
 772:      *                               requested fields.
 773:      *
 774:      * @return mixed  The requested data.
 775:      * @throws Turba_Exception
 776:      */
 777:     public function export($uid, $contentType, $sources = null, $fields = null)
 778:     {
 779:         global $cfgSources, $prefs;
 780: 
 781:         /* Get default address book from user preferences. */
 782:         if (empty($sources)) {
 783:             $sources = @unserialize($prefs->getValue('sync_books'));
 784:         } elseif (!is_array($sources)) {
 785:             $sources = array($sources);
 786:         }
 787:         if (empty($sources)) {
 788:             $sources = array(Turba::getDefaultAddressbook());
 789:         }
 790:         if (empty($sources)) {
 791:             throw new Turba_Exception(_("No address book specified"));
 792:         }
 793: 
 794:         foreach ($sources as $source) {
 795:             if (empty($source) || !isset($cfgSources[$source])) {
 796:                 throw new Turba_Exception(sprintf(_("Invalid address book: %s"), $source));
 797:             }
 798: 
 799:             if (empty($uid)) {
 800:                 throw new Turba_Exception(_("Invalid ID"));
 801:             }
 802: 
 803:             $driver = $GLOBALS['injector']->getInstance('Turba_Factory_Driver')->create($source);
 804: 
 805:             if (!$driver->hasPermission(Horde_Perms::READ)) {
 806:                 continue;
 807:             }
 808: 
 809:             $result = $driver->search(array('__uid' => $uid));
 810:             if (count($result) == 0) {
 811:                 continue;
 812:             } elseif (count($result) > 1) {
 813:                 throw new Turba_Exception("Internal Horde Error: multiple turba objects with same objectId.");
 814:             }
 815: 
 816:             $version = '3.0';
 817:             list($contentType,) = explode(';', $contentType);
 818:             switch ($contentType) {
 819:             case 'text/x-vcard':
 820:                 $version = '2.1';
 821: 
 822:             case 'text/vcard':
 823:             case 'text/directory':
 824:                 $export = '';
 825:                 foreach ($result->objects as $obj) {
 826:                     $vcard = $driver->tovCard($obj, $version, $fields);
 827:                     /* vCards are not enclosed in
 828:                      * BEGIN:VCALENDAR..END:VCALENDAR.  Export the individual
 829:                      * cards instead. */
 830:                     $export .= $vcard->exportvCalendar();
 831:                 }
 832:                 return $export;
 833: 
 834:             case 'array':
 835:                 $attributes = array();
 836:                 foreach ($result->objects as $object) {
 837:                     foreach ($cfgSources[$source]['map'] as $field => $map) {
 838:                         $attributes[$field] = $object->getValue($field);
 839:                     }
 840:                 }
 841: 
 842:                 return $attributes;
 843: 
 844:             case 'activesync':
 845:                 foreach ($result->objects as $object) {
 846:                     $return = $object;
 847:                 }
 848: 
 849:                 return $driver->toASContact($return);
 850:             }
 851: 
 852:             throw new Turba_Exception(sprintf(_("Unsupported Content-Type: %s"), $contentType));
 853:         }
 854: 
 855:         throw new Turba_Exception(_("Object not found"));
 856:     }
 857: 
 858:     /**
 859:      * Exports the user's own contact as a vCard string.
 860:      *
 861:      * @return string  The requested vCard data.
 862:      * @throws Turba_Exception
 863:      */
 864:     public function ownVCard()
 865:     {
 866:         $contact = $this->getOwnContactObject();
 867:         $driver = $GLOBALS['injector']->getInstance('Turba_Factory_Driver')->create($contact['source']);
 868: 
 869:         $vcard = $driver->tovCard($contact['contact'], '3.0', null, true);
 870:         $vcard->setAttribute('VERSION', '3.0');
 871: 
 872:         return $vcard->exportvCalendar();
 873:     }
 874: 
 875:     /**
 876:      * Export the user's own contact as a hash
 877:      *
 878:      * @return array  The contact hash.
 879:      * @throws Turba_Exception
 880:      */
 881:     public function ownContact()
 882:     {
 883:         $contact = $this->getOwnContactObject();
 884:         return $contact['contact']->getAttributes();
 885:     }
 886: 
 887:     /**
 888:      * Helper function to  return the user's own contact object
 889:      *
 890:      * @return array  A hash containing the Turba_Object representing the
 891:      *                user's own contact and the source that it is from.
 892:      * @throws Turba_Exception
 893:      */
 894:     public function getOwnContactObject()
 895:     {
 896:         global $cfgSources;
 897: 
 898:         $own_contact = $GLOBALS['prefs']->getValue('own_contact');
 899:         if (empty($own_contact)) {
 900:             throw new Turba_Exception(_("You didn't mark a contact as your own yet."));
 901:         }
 902:         @list($source, $id) = explode(';', $own_contact);
 903: 
 904:         if (!isset($cfgSources[$source])) {
 905:             throw new Turba_Exception(_("The address book with your own contact doesn't exist anymore."));
 906:         }
 907: 
 908:         $driver = $GLOBALS['injector']->getInstance('Turba_Factory_Driver')->create($source);
 909: 
 910:         if (!$driver->hasPermission(Horde_Perms::READ)) {
 911:             throw new Turba_Exception(_("You don't have sufficient permissions to read the address book that contains your own contact."));
 912:         }
 913: 
 914:         try {
 915:             $contact = $driver->getObject($id);
 916:         } catch (Turba_Exception $e) {
 917:             throw new Turba_Exception(_("Your own contact cannot be found in the address book."));
 918:         }
 919: 
 920:         return array(
 921:             'contact' => $contact,
 922:             'source'=> $source
 923:         );
 924:     }
 925: 
 926:     /**
 927:      * Deletes a contact identified by UID.
 928:      *
 929:      * @param string|array $uid      Identify the contact to delete, either a
 930:      *                               single UID or an array.
 931:      * @param string|array $sources  The source(s) from which the contact will
 932:      *                               be deleted.
 933:      *
 934:      * @return boolean  Success or failure.
 935:      * @throws Turba_Exception
 936:      */
 937:     public function delete($uid, $sources = null)
 938:     {
 939:         // Handle an array of UIDs for convenience of deleting multiple contacts
 940:         // at once.
 941:         if (is_array($uid)) {
 942:             foreach ($uid as $g) {
 943:                 $this->delete($g, $source);
 944:             }
 945: 
 946:             return true;
 947:         }
 948: 
 949:         global $cfgSources, $prefs;
 950: 
 951:         /* Get default address book from user preferences. */
 952:         if (empty($sources)) {
 953:             $sources = @unserialize($prefs->getValue('sync_books'));
 954:         } elseif (!is_array($sources)) {
 955:             $sources = array($sources);
 956:         }
 957:         if (empty($sources)) {
 958:             $sources = array(Turba::getDefaultAddressbook());
 959:         }
 960:         if (empty($sources)) {
 961:             throw new Turba_Exception(_("No address book specified"));
 962:         }
 963: 
 964:         foreach ($sources as $source) {
 965:             if (empty($source) || !isset($cfgSources[$source])) {
 966:                 throw new Turba_Exception(sprintf(_("Invalid address book: %s"), $source));
 967:             }
 968: 
 969:             if (empty($uid)) {
 970:                 throw new Turba_Exception(_("Invalid ID"));
 971:             }
 972: 
 973:             $driver = $GLOBALS['injector']->getInstance('Turba_Factory_Driver')->create($source);
 974: 
 975:             if (!$GLOBALS['registry']->isAdmin() &&
 976:                 !$driver->hasPermission(Horde_Perms::DELETE)) {
 977:                 continue;
 978:             }
 979: 
 980:             // If the objectId isn't in $source in the first place, just return
 981:             // true. Otherwise, try to delete it and return success or failure.
 982:             $result = $driver->search(array('__uid' => $uid));
 983:             if (count($result) == 0) {
 984:                 continue;
 985:             }
 986: 
 987:             $r = $result->objects[0];
 988:             return $driver->delete($r->getValue('__key'));
 989:         }
 990: 
 991:         return true;
 992:     }
 993: 
 994:     /**
 995:      * Replaces the contact identified by UID with the content represented in
 996:      * the specified contentType.
 997:      *
 998:      * @param string $uid            Idenfity the contact to replace.
 999:      * @param string $content        The content of the contact.
1000:      * @param string $contentType    What format is the data in? Currently
1001:      *                               supports array, text/directory,
1002:      *                               text/vcard, text/x-vcard and activesync.
1003:      * @param string|array $sources  The source(s) where the contact will be
1004:      *                               replaced.
1005:      *
1006:      * @return boolean  Success or failure.
1007:      * @throws Turba_Exception
1008:      */
1009:     public function replace($uid, $content, $contentType, $sources = null)
1010:     {
1011:         global $cfgSources, $prefs;
1012: 
1013:         /* Get default address book from user preferences. */
1014:         if (empty($sources)) {
1015:             $sources = @unserialize($prefs->getValue('sync_books'));
1016:         } elseif (!is_array($sources)) {
1017:             $sources = array($sources);
1018:         }
1019:         if (empty($sources)) {
1020:             $sources = array(Turba::getDefaultAddressbook());
1021:         }
1022:         if (empty($sources)) {
1023:             throw new Turba_Exception(_("No address book specified"));
1024:         }
1025: 
1026:         foreach ($sources as $source) {
1027:             if (empty($source) || !isset($cfgSources[$source])) {
1028:                 throw new Turba_Exception(sprintf(_("Invalid address book: %s"), $source));
1029:             }
1030: 
1031:             if (empty($uid)) {
1032:                 throw new Turba_Exception(_("Invalid contact unique ID"));
1033:             }
1034: 
1035:             // Check permissions.
1036:             $driver = $GLOBALS['injector']->getInstance('Turba_Factory_Driver')->create($source);
1037:             if (!$driver->hasPermission(Horde_Perms::EDIT)) {
1038:                 continue;
1039:             }
1040:             $result = $driver->search(array('__uid' => $uid));
1041:             if (!count($result)) {
1042:                 continue;
1043:             } elseif (count($result) > 1) {
1044:                 throw new Turba_Exception(_("Multiple contacts found with same unique ID."));
1045:             }
1046: 
1047:             $object = $result->objects[0];
1048: 
1049:             switch ($contentType) {
1050:             case 'array':
1051:                 break;
1052: 
1053:             case 'text/x-vcard':
1054:             case 'text/vcard':
1055:             case 'text/directory':
1056:                 $iCal = new Horde_Icalendar();
1057:                 if (!$iCal->parsevCalendar($content)) {
1058:                     throw new Turba_Exception(_("There was an error importing the iCalendar data."));
1059:                 }
1060: 
1061:                 switch ($iCal->getComponentCount()) {
1062:                 case 0:
1063:                     throw new Turba_Exception(_("No vCard data was found."));
1064: 
1065:                 case 1:
1066:                     $content = $iCal->getComponent(0);
1067:                     $content = $driver->toHash($content);
1068:                     break;
1069: 
1070:                 default:
1071:                     throw new Turba_Exception(_("Only one vcard supported."));
1072:                 }
1073:                 break;
1074: 
1075:             case 'activesync':
1076:                 $content = $driver->fromASContact($content);
1077:                 /* Must check for ghosted properties for activesync requests */
1078:                 foreach ($content as $attribute => $value) {
1079:                     if ($attribute != '__key') {
1080:                         $object->setValue($attribute, $value);
1081:                     }
1082:                 }
1083:                 return $object->store();
1084:                 break;
1085: 
1086:             default:
1087:                 throw new Turba_Exception(sprintf(_("Unsupported Content-Type: %s"), $contentType));
1088:             }
1089: 
1090:             foreach ($content as $attribute => $value) {
1091:                 if ($attribute != '__key') {
1092:                     $object->setValue($attribute, $value);
1093:                 }
1094:             }
1095: 
1096:             return $object->store();
1097:         }
1098: 
1099:         throw new Turba_Exception(_("Object not found"));
1100:     }
1101: 
1102:     /**
1103:      * Returns a contact search result.
1104:      *
1105:      * @param array $names          The search filter values.
1106:      * @param array $sources        The sources to search in.
1107:      * @param array $fields         The fields to search on.
1108:      * @param boolean $matchBegin   Match word boundaries only?
1109:      * @param boolean $forceSource  Whether to use the specified sources, even
1110:      *                              if they have been disabled in the
1111:      *                              preferences?
1112:      * @param array $returnFields   Only return these fields. Returns all fields
1113:      *                              if empty.
1114:      * @param boolean $count_only   Only return the count of matching entries,
1115:      *                              not the entries themselves.
1116:      *
1117:      * @return array  Hash containing the search results.
1118:      * @throws Turba_Exception
1119:      */
1120:     public function search($names = array(), $sources = array(),
1121:                            $fields = array(), $matchBegin = false,
1122:                            $forceSource = false, $returnFields = array(),
1123:                            $count_only = false)
1124:     {
1125:         global $cfgSources, $attributes, $prefs;
1126: 
1127:         if (!isset($cfgSources) || !is_array($cfgSources) || !count($cfgSources)) {
1128:             return array();
1129:         }
1130: 
1131:         if (!is_array($names)) {
1132:             $names = is_null($names) ? array() : array($names);
1133:         }
1134: 
1135:         if (!$forceSource) {
1136:             // Make sure the selected source is activated in Turba.
1137:             $addressbooks = array_keys(Turba::getAddressBooks());
1138:             foreach (array_keys($sources) as $id) {
1139:                 if (!in_array($sources[$id], $addressbooks)) {
1140:                     unset($sources[$id]);
1141:                 }
1142:             }
1143:         }
1144: 
1145:         // ...and ensure the default source is used as a default.
1146:         if (!count($sources)) {
1147:             $sources = array(Turba::getDefaultAddressbook());
1148:         }
1149: 
1150:         // Read the columns to display from the preferences.
1151:         $sort_columns = Turba::getColumns();
1152: 
1153:         if ($count_only) {
1154:             $results = 0;
1155:         } else {
1156:             $results = array();
1157:         }
1158:         $seen = array();
1159:         foreach ($sources as $source) {
1160:             // Skip invalid sources.
1161:             if (!isset($cfgSources[$source])) {
1162:                 continue;
1163:             }
1164: 
1165:             // Skip sources that aren't browseable if the search is empty.
1166:             if (empty($cfgSources[$source]['browse']) &&
1167:                 (!count($names) || (count($names) == 1 && empty($names[0])))) {
1168:                     continue;
1169:                 }
1170: 
1171:             $driver = $GLOBALS['injector']
1172:                 ->getInstance('Turba_Factory_Driver')
1173:                 ->create($source);
1174: 
1175:             // Determine the name of the column to sort by.
1176:             $columns = isset($sort_columns[$source])
1177:                 ? $sort_columns[$source] : array();
1178: 
1179:             foreach ($names as $name) {
1180:                 $trimname = trim($name);
1181:                 $criteria = array();
1182:                 if (strlen($trimname)) {
1183:                     if (isset($fields[$source])) {
1184:                         foreach ($fields[$source] as $field) {
1185:                             $criteria[$field] = $trimname;
1186:                         }
1187:                     }
1188:                     if (count($criteria) == 0) {
1189:                         $criteria['name'] = $trimname;
1190:                     }
1191:                 }
1192:                 $search = $driver->search(
1193:                     $criteria, Turba::getPreferredSortOrder(), 'OR', $returnFields, array(), $matchBegin, $count_only);
1194:                 if ($count_only) {
1195:                     $results += $search;
1196:                     continue;
1197:                 } elseif (!($search instanceof Turba_List)) {
1198:                     continue;
1199:                 }
1200: 
1201:                 while ($ob = $search->next()) {
1202:                     if (!$ob->isGroup()) {
1203:                         /* Not a group. */
1204:                         $att = array('__key' => $ob->getValue('__key'));
1205:                         foreach ($ob->driver->getCriteria() as $info_key => $info_val) {
1206:                             $att[$info_key] = $ob->getValue($info_key);
1207:                         }
1208:                         $email = array();
1209:                         foreach (array_keys($att) as $key) {
1210:                             if (!$ob->getValue($key) ||
1211:                                 !isset($attributes[$key]) ||
1212:                                 $attributes[$key]['type'] != 'email') {
1213:                                     continue;
1214:                             }
1215:                             $email_val = $ob->getValue($key);
1216: 
1217:                             // Multiple addresses support
1218:                             if (isset($attributes[$key]['params'])
1219:                                 && is_array($attributes[$key]['params'])
1220:                                 && !empty($attributes[$key]['params']['allow_multi'])) {
1221:                                 $addrs = Horde_Mime_Address::explode($email_val);
1222:                             } else {
1223:                                 $addrs = array($email_val);
1224:                             }
1225: 
1226:                             foreach ($addrs as $addr) {
1227:                                 $email[] = trim($addr);
1228:                             }
1229:                         }
1230: 
1231:                         if ($ob->hasValue('name') ||
1232:                             !isset($ob->driver->alternativeName)) {
1233:                             $display_name = Turba::formatName($ob);
1234:                         } else {
1235:                             $display_name = $ob->getValue($ob->driver->alternativeName);
1236:                         }
1237:                         if (count($email)) {
1238:                             for ($i = 0; $i < count($email); $i++) {
1239:                                 $seen_key = trim(Horde_String::lower($display_name)) . '/' . trim(Horde_String::lower($email[$i]));
1240:                                 if (!empty($seen[$seen_key])) {
1241:                                     continue;
1242:                                 }
1243:                                 $seen[$seen_key] = true;
1244:                                 if (!isset($results[$name])) {
1245:                                     $results[$name] = array();
1246:                                 }
1247:                                 $results[$name][] = array_merge($att,
1248:                                     array('id' => $att['__key'],
1249:                                     'name' => $display_name,
1250:                                     'email' => $email[$i],
1251:                                     '__type' => 'Object',
1252:                                     'source' => $source));
1253:                             }
1254:                         } else {
1255:                             if (!isset($results[$name])) {
1256:                                 $results[$name] = array();
1257:                             }
1258:                             $results[$name][] = array_merge($att,
1259:                                 array('id' => $att['__key'],
1260:                                 'name' => $display_name,
1261:                                 'email' => null,
1262:                                 '__type' => 'Object',
1263:                                 'source' => $source));
1264:                         }
1265:                     } else {
1266:                         /* Is a distribution list. */
1267:                         $listatt = $ob->getAttributes();
1268:                         $seeninlist = array();
1269:                         $members = $ob->listMembers();
1270:                         $listName = $ob->getValue('name');
1271:                         if (!($members instanceof Turba_List)) {
1272:                             continue;
1273:                         }
1274:                         if (count($members)) {
1275:                             if (!isset($results[$name])) {
1276:                                 $results[$name] = array();
1277:                             }
1278:                             $emails = array();
1279:                             while ($ob = $members->next()) {
1280:                                 $att = $ob->getAttributes();
1281:                                 foreach (array_keys($att) as $key) {
1282:                                     $value = $ob->getValue($key);
1283:                                     if (empty($value)) {
1284:                                         continue;
1285:                                     }
1286:                                     if (!is_array($value)) {
1287:                                         $seen_key = trim(Horde_String::lower($ob->getValue('name')))
1288:                                             . trim(Horde_String::lower($value));
1289:                                     } else {
1290:                                         $seen_key = trim(Horde_String::lower($ob->getValue('name')))
1291:                                             . trim(Horde_String::lower($value['load']['file']));
1292:                                     }
1293:                                     if (isset($attributes[$key]) &&
1294:                                         $attributes[$key]['type'] == 'email' &&
1295:                                         empty($seeninlist[$seen_key])) {
1296:                                             $emails[] = $value;
1297:                                             $seeninlist[$seen_key] = true;
1298:                                         }
1299:                                 }
1300:                             }
1301:                             $results[$name][] = array('name' => $listName,
1302:                                 'email' => implode(', ', $emails),
1303:                                 'id' => $listatt['__key'],
1304:                                 'source' => $source);
1305:                         }
1306:                     }
1307:                 }
1308:             }
1309:         }
1310: 
1311:         return $results;
1312:     }
1313: 
1314:     /**
1315:      * Retrieves a contact.
1316:      *
1317:      * @param string $source    The source name where the contact is stored
1318:      * @param string $objectId  The unique id of the contact to retrieve
1319:      *
1320:      * @return array  The retrieved contact.
1321:      * @throws Turba_Exception
1322:      */
1323:     public function getContact($source = null, $objectId = '')
1324:     {
1325:         global $cfgSources;
1326: 
1327:         if (!isset($cfgSources) || !is_array($cfgSources) || !count($cfgSources)) {
1328:             return array();
1329:         }
1330: 
1331:         if (isset($cfgSources[$source])) {
1332:             $driver = $GLOBALS['injector']->getInstance('Turba_Factory_Driver')->create($source);
1333: 
1334:             $object = $driver->getObject($objectId);
1335: 
1336:             $attributes = array();
1337:             foreach ($cfgSources[$source]['map'] as $field => $map) {
1338:                 $attributes[$field] = $object->getValue($field);
1339:             }
1340:             return $attributes;
1341:         }
1342: 
1343:         return array();
1344:     }
1345: 
1346:     /**
1347:      * Retrieves a set of contacts from a single source.
1348:      *
1349:      * @param string $source    The source name where the contact is stored
1350:      * @param array $objectIds  The unique ids of the contact to retrieve.
1351:      *
1352:      * @return array  The retrieved contact.
1353:      * @throws Turba_Exception
1354:      */
1355:     public function getContacts($source = '', $objectIds = array())
1356:     {
1357:         global $cfgSources;
1358:         $results = array();
1359:         if (!is_array($objectIds)) {
1360:             $objectIds = array($objectIds);
1361:         }
1362: 
1363:         if (!isset($cfgSources) || !is_array($cfgSources) || !count($cfgSources)) {
1364:             return array();
1365:         }
1366: 
1367:         if (isset($cfgSources[$source])) {
1368:             $driver = $GLOBALS['injector']->getInstance('Turba_Factory_Driver')->create($source);
1369: 
1370:             $objects = $driver->getObjects($objectIds);
1371: 
1372:             foreach ($objects as $object) {
1373:                 $attributes = array();
1374:                 foreach ($cfgSources[$source]['map'] as $field => $map) {
1375:                     $attributes[$field] = $object->getValue($field);
1376:                 }
1377:                 $results[] = $attributes;
1378:             }
1379:         }
1380: 
1381:         return $results;
1382:     }
1383: 
1384:     /**
1385:      * Retrieves a list of all possible values of a field in specified
1386:      * source(s).
1387:      *
1388:      * @param string $field   Field name to check
1389:      * @param array $sources  Array containing the sources to look in
1390:      *
1391:      * @return array  An array of fields and possible values.
1392:      * @throws Turba_Exception
1393:      */
1394:     public function getAllAttributeValues($field = '', $sources = array())
1395:     {
1396:         global $cfgSources;
1397: 
1398:         if (!isset($cfgSources) || !is_array($cfgSources) || !count($cfgSources)) {
1399:             return array();
1400:         }
1401: 
1402:         if (!count($sources)) {
1403:             $sources = array(Turba::getDefaultAddressbook());
1404:         }
1405: 
1406:         $results = array();
1407:         foreach ($sources as $source) {
1408:             if (isset($cfgSources[$source])) {
1409:                 $driver = $GLOBALS['injector']->getInstance('Turba_Factory_Driver')->create($source);
1410: 
1411:                 $res = $driver->search(array());
1412:                 if (!($res instanceof Turba_List)) {
1413:                     throw new Turba_Exception(_("Search failed"));
1414:                 }
1415: 
1416:                 while ($ob = $res->next()) {
1417:                     if ($ob->hasValue($field)) {
1418:                         $results[$source . ':' . $ob->getValue('__key')] = array(
1419:                             'name' => $ob->getValue('name'),
1420:                             'email' => $ob->getValue('email'),
1421:                             $field => $ob->getValue($field));
1422:                     }
1423:                 }
1424:             }
1425:         }
1426: 
1427:         return $results;
1428:     }
1429: 
1430:     /**
1431:      * Retrieves a list of available time objects categories
1432:      *
1433:      * @return array  An array of all configured time object categories.
1434:      */
1435:     public function listTimeObjectCategories()
1436:     {
1437:         $categories = array();
1438:         foreach ($GLOBALS['attributes'] as $key => $attribute) {
1439:             if ($attribute['type'] == 'monthdayyear' &&
1440:                 !empty($attribute['time_object_label'])) {
1441:                 foreach ($GLOBALS['cfgSources'] as $srcKey => $source) {
1442:                     if (!empty($source['map'][$key])) {
1443:                         $categories[$key . '/'. $srcKey] = sprintf(_("%s in %s"), $attribute['time_object_label'], $source['title']);
1444:                     }
1445:                 }
1446:             }
1447:         }
1448: 
1449:         return $categories;
1450:     }
1451: 
1452:     /**
1453:      * Lists birthdays and/or anniversaries as time objects.
1454:      *
1455:      * @param array $time_categories  The time categories (from
1456:      *                                listTimeObjectCategories) to list.
1457:      * @param mixed $start            The start date of the period.
1458:      * @param mixed $end              The end date of the period.
1459:      *
1460:      * @return array  An array of timeObject results.
1461:      * @throws Turba_Exception
1462:      */
1463:     public function listTimeObjects($time_categories, $start, $end)
1464:     {
1465:         global $cfgSources;
1466: 
1467:         $start = new Horde_Date($start);
1468:         $end = new Horde_Date($end);
1469: 
1470:         if (!isset($cfgSources) || !is_array($cfgSources) || !count($cfgSources)) {
1471:             return array();
1472:         }
1473: 
1474:         $objects = array();
1475:         foreach ($time_categories as $category) {
1476:             list($category, $source) = explode('/', $category, 2);
1477:             $driver = $GLOBALS['injector']->getInstance('Turba_Factory_Driver')->create($source);
1478:             $objects = array_merge($objects, $driver->listTimeObjects($start, $end, $category));
1479:         }
1480: 
1481:         return $objects;
1482:     }
1483: 
1484:     /**
1485:      * Returns the client source name
1486:      *
1487:      * @return string  The name of the source to use with the clients api.
1488:      */
1489:     public function getClientSource()
1490:     {
1491:         return !empty($GLOBALS['conf']['client']['addressbook']) ? $GLOBALS['conf']['client']['addressbook'] : false;
1492:     }
1493: 
1494:     /**
1495:      * Returns the available client fields
1496:      *
1497:      * @return array  An array describing the fields.
1498:      */
1499:     public function clientFields()
1500:     {
1501:         return $this->fields($GLOBALS['conf']['client']['addressbook']);
1502:     }
1503: 
1504:     /**
1505:      * Returns a contact from the client source.
1506:      *
1507:      * @param string $objectId  Client unique ID
1508:      *
1509:      * @return array  Array of client data.
1510:      * @throws Turba_Exception
1511:      */
1512:     public function getClient($objectId = '')
1513:     {
1514:         return $this->getContact($GLOBALS['conf']['client']['addressbook'], $objectId);
1515:     }
1516: 
1517:     /**
1518:      * Returns mulitple contacts from the client source
1519:      *
1520:      * @param array $objectIds  client unique ids
1521:      *
1522:      * @return array  An array of clients data.
1523:      * @throws Turba_Exception
1524:      */
1525:     public function getClients($objectIds = array())
1526:     {
1527:         return $this->getContacts($GLOBALS['conf']['client']['addressbook'], $objectIds);
1528:     }
1529: 
1530:     /**
1531:      * Adds a client to the client source
1532:      *
1533:      * @param array $attributes  Array containing the client attributes
1534:      */
1535:     public function addClient($attributes = array())
1536:     {
1537:         return $this->import($attributes, 'array', $this->getClientSource());
1538:     }
1539: 
1540:     /**
1541:      * Updates client data
1542:      *
1543:      * @param string $objectId   The unique id of the client
1544:      * @param array $attributes  An array of client attributes
1545:      *
1546:      * @return boolean
1547:      */
1548:     public function updateClient($objectId = '', $attributes = array())
1549:     {
1550:         return $this->replace($this->getClientSource() . ':' . $objectId, $attributes, 'array');
1551:     }
1552: 
1553:     /**
1554:      * Deletes a client
1555:      *
1556:      * @param string $objectId  The unique id of the client
1557:      *
1558:      * @return boolean
1559:      */
1560:     public function deleteClient($objectId = '')
1561:     {
1562:         return $this->delete($this->getClientSource() . ':' . $objectId);
1563:     }
1564: 
1565:     /**
1566:      * Search for clients
1567:      *
1568:      * @param array $names         The search filter values
1569:      * @param array $fields        The fields to search in
1570:      * @param boolean $matchBegin  Match word boundaries only
1571:      *
1572:      * @return array  A hash containing the search results.
1573:      * @throws Turba_Exception
1574:      */
1575:     public function searchClients($names = array(), $fields = array(),
1576:                                   $matchBegin = false)
1577:     {
1578:         return $this->search(
1579:             $names,
1580:             array($GLOBALS['conf']['client']['addressbook']),
1581:             array($GLOBALS['conf']['client']['addressbook'] => $fields),
1582:             $matchBegin,
1583:             true
1584:         );
1585:     }
1586: 
1587:     /**
1588:      * Sets the value of the specified attribute of a contact
1589:      *
1590:      * @param string|array $address  Contact email address(es).
1591:      * @param string $name           Contact name.
1592:      * @param string $field          Field to update.
1593:      * @param string $value          Field value to set.
1594:      * @param string $source         Contact source.
1595:      *
1596:      * @throws Turba_Exception
1597:      */
1598:     public function addField($address = '', $name = '', $field = '',
1599:                              $value = '', $source = '')
1600:     {
1601:         if (is_array($address)) {
1602:             $exception = null;
1603:             $success = 0;
1604:             foreach ($address as $tmp) {
1605:                 try {
1606:                     $this->addField($tmp, $name, $field, $value, $source);
1607:                     $success++;
1608:                 } catch (Exception $exception) {
1609:                 }
1610:             }
1611:             if ($exception) {
1612:                 if ($success) {
1613:                     throw new Turba_Exception(sprintf(ngettext("Added or updated %d contact, but at least one contact failed:", "Added or updated %d contacts, but at least one contact failed:", $success), $success) . ' ' . $exception->getMessage());
1614:                 } else {
1615:                     throw $exception;
1616:                 }
1617:             }
1618:         }
1619: 
1620:         global $cfgSources;
1621: 
1622:         if (empty($source) || !isset($cfgSources[$source])) {
1623:             throw new Turba_Exception(sprintf(_("Invalid address book: %s"), $source));
1624:         }
1625: 
1626:         if (empty($address)) {
1627:             throw new Turba_Exception(_("Invalid email"));
1628:         }
1629: 
1630:         if (empty($name)) {
1631:             throw new Turba_Exception(_("Invalid name"));
1632:         }
1633: 
1634:         if (empty($value)) {
1635:             throw new Turba_Exception(_("Invalid entry"));
1636:         }
1637: 
1638:         $driver = $GLOBALS['injector']->getInstance('Turba_Factory_Driver')->create($source);
1639: 
1640:         if (!$driver->hasPermission(Horde_Perms::EDIT)) {
1641:             throw new Turba_Exception(_("Permission denied"));
1642:         }
1643: 
1644:         try {
1645:             $res = $driver->search(array('email' => trim($address)), null, 'AND');
1646:         } catch (Turba_Exception $e) {
1647:             throw new Turba_Exception(sprintf(_("Search failed: %s"), $res->getMessage()));
1648:         }
1649: 
1650:         if (count($res) > 1) {
1651:             try {
1652:                 $res2 = $driver->search(array('email' => trim($address), 'name' => trim($name)), null, 'AND');
1653:             } catch (Turba_Exception $e) {
1654:                 throw new Turba_Exception(sprintf(_("Search failed: %s"), $e->getMessage()));
1655:             }
1656: 
1657:             if (!count($res2)) {
1658:                 throw new Turba_Exception(sprintf(_("Multiple persons with address [%s], but none with name [%s] already exist"), trim($address), trim($name)));
1659:             }
1660: 
1661:             try {
1662:                 $res3 = $driver->search(array('email' => $address, 'name' => $name, $field => $value));
1663:             } catch (Turba_Exception $e) {
1664:                 throw new Turba_Exception(sprintf(_("Search failed: %s"), $e->getMessage()));
1665:             }
1666: 
1667:             if (count($res3)) {
1668:                 throw new Turba_Exception(sprintf(_("This person already has a %s entry in the address book"), $field));
1669:             }
1670: 
1671:             $ob = $res2->next();
1672:             $ob->setValue($field, $value);
1673:             $ob->store();
1674:         } elseif (count($res) == 1) {
1675:             try {
1676:                 $res4 = $driver->search(array('email' => $address, $field => $value));
1677:             } catch (Turba_Exception $e) {
1678:                 throw new Turba_Exception(sprintf(_("Search failed: %s"), $e->getMessage()));
1679:             }
1680: 
1681:             if (count($res4)) {
1682:                 throw new Turba_Exception(sprintf(_("This person already has a %s entry in the address book"), $field));
1683:             }
1684: 
1685:             $ob = $res->next();
1686:             $ob->setValue($field, $value);
1687:             $ob->store();
1688:         } else {
1689:             $driver->add(array('email' => $address, 'name' => $name, $field => $value, '__owner' => $GLOBALS['registry']->getAuth()));
1690:         }
1691:     }
1692: 
1693:     /**
1694:      * Returns a field value
1695:      *
1696:      * @param string $address    Contact email address
1697:      * @param string $field      Field to get
1698:      * @param array $sources     Sources to check
1699:      * @param boolean $strict    Match the email address strictly
1700:      * @param boolean $multiple  Return more than one entry if found and true,
1701:      *                           return an error if this is false.
1702:      *
1703:      * @return array  An array of field value(s).
1704:      * @throws Turba_Exception
1705:      */
1706:     public function getField($address = '', $field = '', $sources = array(),
1707:                              $strict = false, $multiple = false)
1708:     {
1709:         global $cfgSources;
1710: 
1711:         if (empty($address)) {
1712:             throw new Turba_Exception(_("Invalid email"));
1713:         }
1714: 
1715:         if (!isset($cfgSources) || !is_array($cfgSources) || !count($cfgSources)) {
1716:             return array();
1717:         }
1718: 
1719:         if (!count($sources)) {
1720:             $sources = array(Turba::getDefaultAddressbook());
1721:         }
1722: 
1723:         $result = array();
1724:         foreach ($sources as $source) {
1725:             if (!isset($cfgSources[$source])) {
1726:                 continue;
1727:             }
1728: 
1729:             $driver = $GLOBALS['injector']->getInstance('Turba_Factory_Driver')->create($source);
1730:             $criterium = array('email' => $address);
1731:             if (!isset($driver->map['email'])) {
1732:                 if (isset($driver->map['emails'])) {
1733:                     $criterium = array('emails' => $address);
1734:                 } else {
1735:                     continue;
1736:                 }
1737:             }
1738: 
1739:             try {
1740:                 $list = $driver->search($criterium, null, 'AND', array(), $strict ? array('email') : array());
1741:             } catch (Turba_Exception $e) {
1742:                 Horde::logMessage($e, 'ERR');
1743:             }
1744:             if (!($list instanceof Turba_List)) {
1745:                 continue;
1746:             }
1747: 
1748:             while ($ob = $list->next()) {
1749:                 if ($ob->hasValue($field)) {
1750:                     $result[] = $ob->getValue($field);
1751:                 }
1752:             }
1753:         }
1754: 
1755:         if (count($result) > 1) {
1756:             if ($multiple) {
1757:                 return $result;
1758:             } else {
1759:                 throw new Turba_Exception(_("More than 1 entry found"));
1760:             }
1761:         } elseif (empty($result)) {
1762:             throw new Turba_Exception(sprintf(_("No %s entry found for %s"), $field, $address));
1763:         }
1764: 
1765:         return reset($result);
1766:     }
1767: 
1768:     /**
1769:      * Deletes a field value
1770:      *
1771:      * @param string $address Contact email address
1772:      * @param string $field   Field to delete value for
1773:      * @param array $sources  Sources to delete value from
1774:      *
1775:      * @return boolean  TODO
1776:      * @throws Turba_Exception
1777:      */
1778:     public function deleteField($address = '', $field = '', $sources = array())
1779:     {
1780:         global $cfgSources;
1781: 
1782:         if (empty($address)) {
1783:             throw new Turba_Exception(_("Invalid email"));
1784:         }
1785: 
1786:         if (!isset($cfgSources) || !is_array($cfgSources) || !count($cfgSources)) {
1787:             return array();
1788:         }
1789: 
1790:         if (count($sources) == 0) {
1791:             $sources = array(Turba::getDefaultAddressbook());
1792:         }
1793: 
1794:         $success = false;
1795: 
1796:         foreach ($sources as $source) {
1797:             if (isset($cfgSources[$source])) {
1798:                 $driver = $GLOBALS['injector']->getInstance('Turba_Factory_Driver')->create($source);
1799:                 if (!$driver->hasPermission(Horde_Perms::EDIT)) {
1800:                     continue;
1801:                 }
1802: 
1803:                 $res = $driver->search(array('email' => $address));
1804:                 if ($res instanceof Turba_List) {
1805:                     if (count($res) > 1) {
1806:                         continue;
1807:                     }
1808: 
1809:                     $ob = $res->next();
1810:                     if (is_object($ob) && $ob->hasValue($field)) {
1811:                         $ob->setValue($field, '');
1812:                         $ob->store();
1813:                         $success = true;
1814:                     }
1815:                 }
1816:             }
1817:         }
1818: 
1819:         if (!$success) {
1820:             throw new Turba_Exception(sprintf(_("No %s entry found for %s"), $field, $address));
1821:         }
1822:     }
1823: 
1824:     /**
1825:      * Obtain an array of $cfgSource entries matching the filter criteria.
1826:      *
1827:      * @param type $filter  A single key -> value hash to filter the sources.
1828:      *
1829:      * @return array
1830:      */
1831:     public function getSourcesConfig($filter = array())
1832:     {
1833:          // Get a list of all available Turba sources
1834:         $turba_sources = Horde::loadConfiguration('backends.php',
1835:                                                   'cfgSources', 'turba');
1836:         $results = array();
1837:         foreach ($turba_sources as $key => $source) {
1838:             if (!empty($filter)) {
1839:                 if(!empty($source[current(array_keys($filter))]) &&
1840:                    $source[current(array_keys($filter))] == current($filter)) {
1841: 
1842:                     $results[$key] = $source;
1843: 
1844:                 }
1845:             }
1846:         }
1847: 
1848:         return $results;
1849:     }
1850: 
1851:     /**
1852:      * Lists all shares the current user has access to.
1853:      *
1854:      * @param integer $perms
1855:      *
1856:      * @return  array of Turba_Share objects.
1857:      */
1858:     public function listShares($perms = Horde_Perms::READ)
1859:     {
1860:         return Turba::listShares(true, $perms);
1861:     }
1862: 
1863:     /**
1864:      * GroupObject API - Lists all turba lists for the current user that can be
1865:      * treated as Horde_Group objects.
1866:      *
1867:      * @return array  A hash of all visible groups in the form of
1868:      *                group_id => group_name
1869:      * @throws Horde_Exception
1870:      */
1871:     public function listUserGroupObjects()
1872:     {
1873:         $groups = $owners = array();
1874: 
1875:         // Only turba's SQL based sources can act as Horde_Groups
1876:         $sources = $this->getSourcesConfig(array('type' => 'sql'));
1877: 
1878:         foreach ($sources as $key => $source) {
1879:             // Each source could have a different database connection
1880:             $db[$key] = empty($source['params']['sql'])
1881:                     ? $GLOBALS['injector']->getInstance('Horde_Db_Adapter')
1882:                     : $GLOBALS['injector']->getInstance('Horde_Core_Factory_Db')->create('turba', $source['params']['sql']);
1883: 
1884:             if ($source['use_shares']) {
1885:                 if (empty($contact_shares)) {
1886:                     $contact_shares = $this->listShares(Horde_Perms::SHOW);
1887:                 }
1888:                 foreach ($contact_shares as $id => $share) {
1889:                     $params = @unserialize($share->get('params'));
1890:                     if ($params['source'] == $key) {
1891:                         $owners[] = $params['name'];
1892:                     }
1893:                 }
1894:                 if (!$owners) {
1895:                     return array();
1896:                 }
1897:             } else {
1898:                 $owners = array($GLOBALS['registry']->getAuth());
1899:             }
1900: 
1901:             $owner_ids = array();
1902:             foreach ($owners as $owner) {
1903:                 $owner_ids[] = $db[$key]->quoteString($owner);
1904:             }
1905: 
1906:             $sql = 'SELECT ' . $source['map']['__key'] . ', ' . $source['map'][$source['list_name_field']]
1907:                 . '  FROM ' . $source['params']['table'] . ' WHERE '
1908:                 . $source['map']['__type'] . ' = \'Group\' AND '
1909:                 . $source['map']['__owner'] . ' IN (' . implode(',', $owner_ids ) . ')';
1910: 
1911:             try {
1912:                 $results = $db[$key]->selectAssoc($sql);
1913:             } catch (Horde_Db_Exception $e) {
1914:                 Horde::logMessage($e);
1915:                 throw new Horde_Exception_Wrapped($e);
1916:             }
1917:             foreach ($results as $id => $name) {
1918:                 $groups[$key . ':' . $id] = $name;
1919:             }
1920:         }
1921: 
1922:         return $groups;
1923:     }
1924: 
1925:     /**
1926:      * Returns all contact groups.
1927:      *
1928:      * @return array  A list of group hashes.
1929:      * @throws Turba_Exception
1930:      */
1931:     public function getGroupObjects()
1932:     {
1933:         $ret = array();
1934: 
1935:         foreach ($this->getSourcesConfig(array('type' => 'sql')) as $key => $source) {
1936:             if (empty($source['map']['__type'])) {
1937:                 continue;
1938:             }
1939: 
1940:             list($db, $sql) = $this->_getGroupObject($source, 'Group');
1941: 
1942:             try {
1943:                 $results = $db->selectAll($sql);
1944:             } catch (Horde_Db_Exception $e) {
1945:                 Horde::logMessage($e);
1946:                 throw new Turba_Exception($e);
1947:             }
1948: 
1949:             foreach ($results as $row) {
1950:                 /* name is a reserved word in Postgresql (at a minimum). */
1951:                 $row['name'] = $row['lname'];
1952:                 unset($row['lname']);
1953:                 $ret[$key . ':' . $row['id']] = $row;
1954:             }
1955:         }
1956: 
1957:         return $ret;
1958:     }
1959: 
1960:     /**
1961:      * Returns all contact groups that the specified user is a member of.
1962:      *
1963:      * @param string $user           The user
1964:      * @param boolean $parentGroups  Include user as a member of the any
1965:      *                               parent group as well.
1966:      *
1967:      * @return array  An array of group identifiers that the specified user is a
1968:      *                member of.
1969:      * @throws Horde_Exception
1970:      */
1971:     public function getGroupMemberships($user, $parentGroups = false)
1972:     {
1973:         $lists = $this->getGroupObjects();
1974:         $memberships = array();
1975:         foreach ($lists as $id => $list) {
1976:             $members = $this->getGroupMembers($id, $parentGroups);
1977:             if (in_array($user, $members)) {
1978:                 $memberships[$id] = $list['name'];
1979:             }
1980:         }
1981:         return $memberships;
1982:     }
1983: 
1984:     /**
1985:      * Returns a contact group hash.
1986:      *
1987:      * @param string $gid  The group identifier.
1988:      *
1989:      * @return array  A hash defining the group.
1990:      * @throws Turba_Exception
1991:      */
1992:     public function getGroupObject($gid)
1993:     {
1994:         if (empty($gid) || (strpos($gid, ':') === false)) {
1995:             throw new Turba_Exception(sprintf('Unsupported group id: %s', $gid));
1996:         }
1997: 
1998:         $sources = $this->getSourcesConfig(array('type' => 'sql'));
1999:         list($source, $id) = explode(':', $gid);
2000:         if (empty($sources[$source])) {
2001:             return array();
2002:         }
2003: 
2004:         list($db, $sql) = $this->_getGroupObject($sources[$source], $id);
2005: 
2006:         try {
2007:             $ret = $db->selectOne($sql);
2008:             $ret['name'] = $ret['lname'];
2009:             unset($ret['lname']);
2010:             return $ret;
2011:         } catch (Horde_Db_Exception $e) {
2012:             Horde::logMessage($e);
2013:             throw new Turba_Exception($e);
2014:         }
2015:     }
2016: 
2017:     /**
2018:      */
2019:     protected function _getGroupObject($source, $key)
2020:     {
2021:         $db = empty($source['params']['sql'])
2022:             ? $GLOBALS['injector']->getInstance('Horde_Db_Adapter')
2023:             : $GLOBALS['injector']->getInstance('Horde_Core_Factory_Db')->create('turba', $source['params']['sql']);
2024: 
2025:         $sql = 'SELECT ' . $source['map']['__members'] . ' members,'
2026:             . $source['map']['email'] . ' email,'
2027:             . $source['map'][$source['list_name_field']]
2028:             . ' lname FROM ' . $source['params']['table'] . ' WHERE '
2029:             . $source['map']['__key'] . ' = ' . $db->quoteString($key);
2030: 
2031:         return array($db, $sql);
2032:     }
2033: 
2034:     /**
2035:      * Returns a list of all members belonging to a contact group.
2036:      *
2037:      * @param string $gid         The group identifier
2038:      * @param boolean $subGroups  Also include members of any subgroups?
2039:      *
2040:      * @return array An array of group members (identified by email address).
2041:      * @throws Horde_Exception
2042:      */
2043:     public function getGroupMembers($gid, $subGroups = false)
2044:     {
2045:         $contact_shares = $this->listShares(Horde_Perms::SHOW);
2046:         $sources = $this->getSourcesConfig(array('type' => 'sql'));
2047: 
2048:         $entry = $this->getGroupObject($gid);
2049:         if (!$entry) {
2050:             return array();
2051:         }
2052:         list($source, $id) = explode(':', $gid);
2053:         $members = @unserialize($entry['members']);
2054:         if (!is_array($members)) {
2055:             return array();
2056:         }
2057: 
2058:         $db[$source] = empty($sources[$source]['params']['sql'])
2059:             ? $GLOBALS['injector']->getInstance('Horde_Db_Adapter')
2060:             : $GLOBALS['injector']->getInstance('Horde_Core_Factory_Db')->create('turba', $sources[$source]['params']['sql']);
2061: 
2062:         $users = array();
2063:         foreach ($members as $member) {
2064:             // Is this member from the same source or a different one?
2065:             if (strpos($member, ':') !== false) {
2066:                 list($newSource, $uid) = explode(':', $member);
2067:                 if (!empty($contact_shares[$newSource])) {
2068:                     $params = @unserialize($contact_shares[$newSource]->get('params'));
2069:                     $newSource = $params['source'];
2070:                     $member = $uid;
2071:                     $db[$newSource] = empty($sources[$newSource]['params']['sql'])
2072:                         ? $GLOBALS['injector']->getInstance('Horde_Db_Adapter')
2073:                         : $GLOBALS['injector']->getInstance('Horde_Core_Factory_Db')->create('turba', $sources[$newSource]['params']['sql']);
2074: 
2075:                 } elseif (empty($sources[$newSource])) {
2076:                     // Last chance, it's not in one of our non-share sources
2077:                     continue;
2078:                 }
2079:             } else {
2080:                 // Same source
2081:                 $newSource = $source;
2082:             }
2083: 
2084:             $type = $sources[$newSource]['map']['__type'];
2085:             $email = $sources[$newSource]['map']['email'];
2086:             $sql = 'SELECT ' . $email . ', ' . $type
2087:                 . ' FROM ' . $sources[$newSource]['params']['table']
2088:                 . ' WHERE ' . $sources[$newSource]['map']['__key']
2089:                 . ' = ' . $db[$newSource]->quoteString($member);
2090: 
2091:             try {
2092:                 $results = $db[$newSource]->selectOne($sql);
2093:             } catch (Horde_Db_Exception $e) {
2094:                 Horde::logMessage($e);
2095:                 throw new Horde_Exception_Wrapped($e);
2096:             }
2097: 
2098:             // Sub-Lists are treated as sub groups the best that we can...
2099:             if ($subGroups && $results[$type] == 'Group') {
2100:                 $users = array_merge($users, $this->getGroupMembers($newSource . ':' . $member));
2101:             }
2102:             if (strlen($results[$email])) {
2103:                 // use a key to dump dups
2104:                 $users[$results[$email]] = true;
2105:             }
2106:         }
2107: 
2108:         ksort($users);
2109:         return array_keys($users);
2110:     }
2111: 
2112: }
2113: 
API documentation generated by ApiGen