Overview

Packages

  • Horde
    • Imsp

Classes

  • Horde_Imsp_Auth_Base
  • Horde_Imsp_Auth_CramMd5
  • Horde_Imsp_Auth_Imtest
  • Horde_Imsp_Auth_Plaintext
  • Horde_Imsp_Book
  • Horde_Imsp_Client_Base
  • Horde_Imsp_Client_Socket
  • Horde_Imsp_Exception
  • Horde_Imsp_Options
  • Horde_Imsp_Translation
  • Overview
  • Package
  • Class
  • Tree
   1: <?php
   2: /**
   3:  * Horde_Imsp_Book Class - provides api for dealing with IMSP
   4:  * address books.
   5:  *
   6:  * Copyright 2002-2012 Horde LLC (http://www.horde.org/)
   7:  *
   8:  * See the enclosed file COPYING for license information (LGPL). If you
   9:  * did not receive this file, see http://www.horde.org/licenses/lgpl21.
  10:  *
  11:  * @author  Michael J Rubinsky <mrubinsk@horde.org>
  12:  * @package Horde_Imsp
  13:  */
  14: class Horde_Imsp_Book
  15: {
  16:     /**
  17:      * Supported ACLs
  18:      *
  19:      */
  20:     const ACL_RIGHTS = 'lrwcda';
  21: 
  22:     /**
  23:      * Sort order.
  24:      *
  25:      * @var string
  26:      */
  27:     public $sort = 'ascend';
  28: 
  29:     /**
  30:      * Horde_Imsp_Client object.
  31:      *
  32:      * @var Horde_Imsp_Client_Base
  33:      */
  34:     protected $_imsp;
  35: 
  36:     /**
  37:      * Parameter list.
  38:      *
  39:      * @var array
  40:      */
  41:     protected $_params;
  42: 
  43:     /**
  44:      * Constructor function.
  45:      *
  46:      * @param array $params Hash containing IMSP parameters.
  47:      */
  48:     public function __construct(Horde_Imsp_Client_Base $client, array $params)
  49:     {
  50:         $this->_params = $params;
  51:         $this->_imsp = $client;
  52:     }
  53: 
  54:     /**
  55:      * Returns an array containing the names of all the address books
  56:      * available to the logged in user.
  57:      *
  58:      * @return mixed Array of address book names
  59:      */
  60:     public function getAddressBookList()
  61:     {
  62:         $command_string = 'ADDRESSBOOK *';
  63: 
  64:         $this->_imsp->send($command_string);
  65: 
  66:         /* Iterate through the response and populate an array of
  67:          * address book names. */
  68:         $server_response = $this->_imsp->receive();
  69:         $abooks = array();
  70:         while (preg_match("/^\* ADDRESSBOOK/", $server_response)) {
  71:             /* If this is an ADDRESSBOOK response, then this will explode as so:
  72:              * [0] and [1] can be discarded
  73:              * [2] = attributes
  74:              * [3] = delimiter
  75:              * [4] = address book name
  76:              */
  77: 
  78:             /* First, check for a {} */
  79:             if (preg_match(Horde_Imsp_Client_Socket::OCTET_COUNT, $server_response, $tempArray)) {
  80:                 $abooks[] = $this->_imsp->receiveStringLiteral($tempArray[2]);
  81:                 /* Get the CRLF at end of ADDRESSBOOK response
  82:                  * that the {} does not include. */
  83:                 $this->_imsp->receiveStringLiteral(2);
  84:             } else {
  85:                 $parts = explode(' ', $server_response);
  86:                 $numParts = count($parts);
  87:                 $name = $parts[4];
  88:                 $firstChar = substr($name, 0, 1);
  89:                 if ($firstChar =="\"") {
  90:                     $name = ltrim($name, "\"");
  91:                     for ($i = 5; $i < $numParts; $i++) {
  92:                         $name .= ' ' . $parts[$i];
  93:                         $lastChar = substr($parts[$i], strlen($parts[$i]) - 1, 1);
  94:                         if ($lastChar == "\"") {
  95:                             $name = rtrim($name, "\"");
  96:                             break;
  97:                         }
  98:                     }
  99:                 }
 100:                 $abooks[] = $name;
 101:             }
 102:             $server_response = $this->_imsp->receive();
 103:         }
 104: 
 105:         if ($server_response != 'OK') {
 106:             $this->_imsp->_logger->err('Did not receive expected response frm server.');
 107:             throw new Horde_Imsp_Exception('Did not receive the expected response from the server.');
 108:         }
 109:         $this->_imsp->_logger->debug('ADDRESSBOOK command OK.');
 110: 
 111:         return $abooks;
 112:     }
 113: 
 114:     /**
 115:      * Returns an array containing the names that match $search
 116:      * critera in the address book named $abook.
 117:      *
 118:      * @param string $abook  Address book name to search.
 119:      * @param mixed $search  Search criteria either a string (name) or an array
 120:      *                       in the form of 'fieldName' => 'searchTerm'.
 121:      *
 122:      * @return array Array of names of the entries that match.
 123:      * @throws Horde_Imsp_Exception
 124:      */
 125:     public function search($abook, $search)
 126:     {
 127:         //If no field => value pairs, assume we are searching name.
 128:         $criteria = array();
 129:         if (!is_array($search)) {
 130:             $criteria['name'] = $search;
 131:         } else {
 132:             $criteria = $search;
 133:         }
 134: 
 135:         $this->_imsp->send('SEARCHADDRESS ', true, false);
 136: 
 137:         // Do we need to send the abook name as {} ?
 138:         if (preg_match(Horde_Imsp_Client_Base::MUST_USE_LITERAL, $abook)) {
 139:             $biBook = sprintf("{%d}", strlen($abook));
 140:             $this->_imsp->send($biBook, false, true, true);
 141:         }
 142: 
 143:         //Start parsing the search array.
 144:         $this->_imsp->send("$abook", false, false);
 145:         $count = count($criteria);
 146:         $current = 1;
 147:         foreach ($criteria as $search_field => $search) {
 148:             $this->_imsp->send(" $search_field ", false, false);
 149:             // How about the search term as a {}.
 150:             if (preg_match(Horde_Imsp_Client_Base::MUST_USE_LITERAL, $search)) {
 151:                 $biSearch = sprintf("{%d}", strlen($search));
 152:                 $this->_imsp->send($biSearch, false, true, true);
 153:                 $this->_imsp->send($search, false, $current == $count);
 154:                 $current++;
 155:             } else {
 156:                 // Only send the CrLf if this is the last field/search atom.
 157:                 $this->_imsp->send('"' . $search . '"', false, $current == $count);
 158:                 $current++;
 159:             }
 160:         }
 161: 
 162:         // Get the response.
 163:         $server_response = $this->_imsp->receive();
 164:         $abookNames = Array();
 165: 
 166:         while (preg_match("/^\* SEARCHADDRESS/", $server_response)) {
 167:             $chopped_response = preg_replace("/^\* SEARCHADDRESS/", '', $server_response);
 168: 
 169:             // Remove any lingering white space in front only.
 170:             $chopped_response = ltrim($chopped_response);
 171: 
 172:             // Get rid of any lingering quotes.
 173:             $temp = preg_replace("/\"/", '', $chopped_response);
 174: 
 175:             if (preg_match("/({)([0-9]{1,})(\}$)/", $temp, $tempArray)) {
 176:                 $dataSize = $tempArray[2];
 177:                 $temp = $this->_imsp->receiveStringLiteral($dataSize);
 178: 
 179:                 /* Get the CRLF since {} does not include it. */
 180:                 $this->_imsp->receiveStringLiteral(2);
 181:             }
 182: 
 183:             $abookNames[] = $temp;
 184: 
 185:             // Get the next response line from the server.
 186:             $server_response = $this->_imsp->receive();
 187:         }
 188: 
 189:         // Should check for OK or BAD here just to be certain.
 190:         switch ($server_response) {
 191:         case 'BAD':
 192:             $this->_imsp->_logger->err('The IMSP server did not understand your request:' . $command_text);
 193:             throw new Horde_Imsp_Exception('The IMSP server did not understand your request: ' . $command_text);
 194:         case 'NO':
 195:             $this->_imsp->_logger->err('IMSP server is unable to perform your request: ' . $this->_imsp->lastRawError);
 196:             throw new Horde_Imsp_Exception('IMSP server is unable to perform your request: ' . $this->_imsp->lastRawError);
 197:         }
 198: 
 199:         /* This allows for no results */
 200:         if (count($abookNames) < 1) {
 201:             return $abookNames;
 202:         }
 203: 
 204:         $this->_imsp->_logger->debug('SEARCHADDRESS command OK');
 205: 
 206:         // Determine the sort direction and perform the sort.
 207:         switch ($this->sort) {
 208:         case 'ascend':
 209:             sort($abookNames);
 210:             break;
 211: 
 212:         case 'descend':
 213:             rsort($abookNames);
 214:             break;
 215:         }
 216: 
 217:         return $abookNames;
 218:     }
 219: 
 220:     /**
 221:      * Returns an associative array of a single address book entry.
 222:      * Note that there will always be a 'name' field.
 223:      *
 224:      * @param string $abook       Name of the address book to search.
 225:      * @param string $entryName  'name' attribute of the entry to retrieve
 226:      *
 227:      * @return array  Array containing entry.
 228:      * @throws Horde_Imsp_Exception
 229:      * @throws Horde_Exception_NotFound
 230:      */
 231:     public function getEntry($abook, $entryName)
 232:     {
 233:         $this->_imsp->send('FETCHADDRESS ', true, false);
 234:         if (preg_match(Horde_Imsp_Client_Base::MUST_USE_LITERAL, $abook)) {
 235:             $biBook = sprintf("{%d}", strlen($abook));
 236:             $this->_imsp->send($biBook, false, true, true);
 237:         }
 238:         $this->_imsp->send("$abook ", false, false);
 239:         if (preg_match(Horde_Imsp_Client_Base::MUST_USE_LITERAL, $entryName)) {
 240:             $biName = sprintf("{%d}", strlen($entryName));
 241:             $this->_imsp->send($biName, false, true, true);
 242:             $this->_imsp->send($entryName, false, true);
 243:         } else {
 244:             $this->_imsp->send("\"$entryName\"", false, true);
 245:         }
 246: 
 247:         $server_response = $this->_imsp->receive();
 248:         switch ($server_response) {
 249:         case 'BAD':
 250:             $this->_imsp->_logger->err('The IMSP server did not understand your request.');
 251:             throw new Horde_Imsp_Exception('The IMSP server did not understand your request');
 252:         case 'NO':
 253:             throw new Horde_Exception_NotFound('No entry in this address book matches your query.');
 254:         }
 255: 
 256:         // Get the data in an associative array.
 257:         $entry = $this->_parseFetchAddressResponse($server_response);
 258: 
 259:         //Get the next server response -- this *should* be the OK response.
 260:         $server_response = $this->_imsp->receive();
 261:         if ($server_response != 'OK') {
 262:             // Unexpected response throw error but still continue on.
 263:             $this->_imsp->_logger->err('Did not receive the expected response from the server.');
 264:         }
 265:         $this->_imsp->_logger->debug('FETCHADDRESS completed OK');
 266: 
 267:         return $entry;
 268:     }
 269: 
 270:     /**
 271:      * Creates a new address book.
 272:      *
 273:      * @param string $abookName FULLY QUALIFIED name such 'jdoe.clients' etc...
 274:      *
 275:      * @throws Horde_Imsp_Exception
 276:      */
 277:     public function createAddressBook($abookName)
 278:     {
 279:         $command_text = 'CREATEADDRESSBOOK ';
 280: 
 281:         if (preg_match(Horde_Imsp_Client_Base::MUST_USE_LITERAL, $abookName)) {
 282:             $biBook = sprintf("{%d}", strlen($abookName));
 283:             $this->_imsp->send($command_text . $biBook, true, true, true);
 284:             $this->_imsp->send($abookName, false, true);
 285:         } else {
 286:             $this->_imsp->send($command_text . $abookName, true, true);
 287:         }
 288: 
 289:         $server_response = $this->_imsp->receive();
 290:         switch ($server_response) {
 291:         case 'OK':
 292:             $this->_imsp->_logger->debug('CREATEADDRESSBOOK completed OK');
 293:             break;
 294:         case 'NO':
 295:             // Could not create abook.
 296:             $this->_imsp->_logger->err('IMSP server is unable to perform your request.');
 297:             throw new Horde_Imsp_Exception('IMSP server is unable to perform your request.');
 298:         case 'BAD':
 299:             $this->_imsp->_logger->err('The IMSP server did not understand your request.');
 300:             throw new Horde_Imsp_Exception('The IMSP server did not understand your request.');
 301:         default:
 302:             // Something unexpected.
 303:             $this->_imsp->_logger->err('Did not receive the expected response from the server.');
 304:             throw new Horde_Imsp_Exception('Did not receive the expected response from the server.');
 305:         }
 306:     }
 307: 
 308:     /**
 309:      * Deletes an address book completely!
 310:      *
 311:      * @param string $abookName Name of address book to delete.
 312:      *
 313:      * @throws Horde_Imsp_Exception
 314:      */
 315:     public function deleteAddressBook($abookName)
 316:     {
 317:         $command_text = 'DELETEADDRESSBOOK ';
 318: 
 319:         // Check need for {}.
 320:         if (preg_match(Horde_Imsp_Client_Base::MUST_USE_LITERAL, $abookName)) {
 321:             $biBook = sprintf("{%d}", strlen($abookName));
 322:             $this->_imsp->send($command_text . $biBook, true, true, true);
 323:             $this->_imsp->send($abookName, false, true);
 324:         } else {
 325:             $this->_imsp->send($command_text . $abookName, true, true);
 326:         }
 327:         $server_response = $this->_imsp->receive();
 328:         switch ($server_response) {
 329:         case 'OK':
 330:             $this->_imsp->_logger->debug('DELETEADDRESSBOOK completed OK');
 331:             break;
 332:         case 'NO':
 333:             // Could not create abook.
 334:             $this->_imsp->_logger->err('IMSP server is unable to perform your request.');
 335:             throw new Horde_Imsp_Exception('IMSP server is unable to perform your request.');
 336:         case 'BAD':
 337:             $this->_imsp->_logger->err('The IMSP server did not understand your request.');
 338:             throw new Horde_Imsp_Exception('The IMSP server did not understand your request.');
 339:         default:
 340:             // Something unexpected.
 341:             $this->_imsp->_logger->err('Did not receive the expected response from the server.');
 342:             throw new Horde_Imsp_Exception('Did not receive the expected response from the server.');
 343:         }
 344:     }
 345: 
 346:     /**
 347:      * Renames an address book.
 348:      *
 349:      * @param string $abookOldName Old name.
 350:      * @param string $abookNewName New address book name.
 351:      *
 352:      * @throws Horde_Imsp_Exception
 353:      */
 354:     public function renameAddressBook($abookOldName, $abookNewName)
 355:     {
 356:         $this->_imsp->send('RENAMEADDRESSBOOK ', true, false);
 357:         if (preg_match(Horde_Imsp_Client_Base::MUST_USE_LITERAL, $abookOldName)) {
 358:             $biOldName = sprintf("{%d}", strlen($abookOldName));
 359:             $this->_imsp->send($biOldName, false, true);
 360:             $this->_imsp->receive();
 361:         }
 362: 
 363:         $this->_imsp->send("$abookOldName ", false, false);
 364:         if (preg_match(Horde_Imsp_Client_Base::MUST_USE_LITERAL, $abookNewName)) {
 365:             $biNewName = sprintf("{%d}", strlen($abookNewName));
 366:             $this->_imsp->send($biNewName, false, true);
 367:             $this->_imsp->receive();
 368:         }
 369:         $this->_imsp->send($abookNewName, false, true);
 370: 
 371:         // Get server response.
 372:         $server_response = $this->_imsp->receive();
 373:         switch ($server_response) {
 374:         case 'NO':
 375:             // Could not create abook.
 376:             $this->_imsp->_logger->err('IMSP server is unable to perform your request.');
 377:             throw new Horde_Imsp_Exception('IMSP server is unable to perform your request.');
 378:         case 'BAD':
 379:             $this->_imsp->_logger->err('The IMSP server did not understand your request.');
 380:             throw new Horde_Imsp_Exception('The IMSP server did not understand your request.');
 381:         case 'OK':
 382:             $this->_imsp->_logger->debug("Address book $abookOldName successfully changed to $abookNewName");
 383:             break;
 384:         default:
 385:             // Something unexpected.
 386:             $this->_imsp->_logger->err('Did not receive the expected response from the server.');
 387:             throw new Horde_Imsp_Exception('Did not receive the expected response from the server.');
 388:         }
 389:     }
 390: 
 391:     /**
 392:      * Adds an address book entry to an address book.
 393:      *
 394:      * @param string $abook     Name of address book to add entry to.
 395:      * @param array $entryInfo  Address book entry information -
 396:      *                          there MUST be a field 'name' containing the
 397:      *                          entry name.
 398:      *
 399:      * @throws Horde_Imsp_Exception
 400:      */
 401:     public function addEntry($abook, array $entryInfo)
 402:     {
 403:         $command_text = '';
 404: 
 405:         // Lock the entry if it already exists.
 406:         $this->lockEntry($abook, $entryInfo['name']);
 407:         $this->_imsp->send('STOREADDRESS ', true, false);
 408: 
 409:         // Take care of the name.
 410:         $entryName = $entryInfo['name'];
 411: 
 412:         // {} for book name?
 413:         if (preg_match(Horde_Imsp_Client_Base::MUST_USE_LITERAL, $abook)) {
 414:             $biBook = sprintf("{%d}", strlen($abook));
 415:             $this->_imsp->send($biBook, false, true);
 416:             $this->_imsp->receive();
 417:         }
 418:         $this->_imsp->send("$abook ", false, false);
 419: 
 420:         // Do we need {} for entry name as well?
 421:         if (preg_match(Horde_Imsp_Client_Base::MUST_USE_LITERAL, $entryName)) {
 422:             $biname = sprintf("{%d}", strlen($entryName));
 423:             $this->_imsp->send($biname, false, true);
 424:             $this->_imsp->receive();
 425:             $this->_imsp->send($entryName, false, false);
 426:         } else {
 427:             $this->_imsp->send("\"$entryName\" ", false, false);
 428:         }
 429: 
 430:         while (list($key, $value) = each($entryInfo)) {
 431:             // Do not sent the key name 'name'.
 432:             if ($key != 'name') {
 433:                 // Protect from extraneous white space
 434:                 $value = trim($value);
 435: 
 436:                 // For some reason, tabs seem to break this.
 437:                 $value = preg_replace("/\t/", "\n\r", $value);
 438: 
 439:                 // Check to see if we need {}
 440:                 if (preg_match(Horde_Imsp_Client_Base::MUST_USE_LITERAL, $value)) {
 441:                     $command_text .= $key . sprintf(" {%d}", strlen($value));
 442:                     $this->_imsp->send($command_text, false, true);
 443:                     $server_response = $this->_imsp->receive();
 444:                     $command_text = '';
 445:                     if (!preg_match("/^\+/",  $server_response)) {
 446:                         $this->_imsp->_logger->err('Did not receive the expected response from the server.');
 447:                         throw new Horde_Imsp_Exception('Did not receive the expected response from the server.');
 448:                     }
 449:                     $this->_imsp->send($value, false, false);
 450:                 } else {
 451:                     // If we are here, then we do not need to send a literal.
 452:                     $value = "\"" . $value . "\"";
 453:                     $command_text .= $key . ' ' . $value . ' ';
 454:                 }
 455:             }
 456:         }
 457: 
 458:         // Send anything that is left of the command.
 459:         $this->_imsp->send($command_text, false, true);
 460:         $server_response = $this->_imsp->receive();
 461: 
 462:         switch ($server_response) {
 463:         case 'NO':
 464:             // Could not create abook.
 465:             $this->_imsp->_logger->err('IMSP server is unable to perform your request.');
 466:             throw new Horde_Imsp_Exception('IMSP server is unable to perform your request.');
 467:         case 'BAD':
 468:             $this->_imsp->_logger->err('The IMSP server did not understand your request.');
 469:             throw new Horde_Imsp_Exception('The IMSP server did not understand your request.');
 470:         }
 471: 
 472:         if ($server_response != 'OK') {
 473:             // Cyrus-IMSP server sends a FETCHADDRESS Response here.
 474:             // Do others?     This was not in the RFC.
 475:             $dummy_array = $this->_parseFetchAddressResponse($server_response);
 476:             $server_response = $this->_imsp->receive();
 477:             switch ($server_response) {
 478:             case 'NO':
 479:                 // Could not create abook.
 480:                 $this->_imsp->_logger->err('IMSP server is unable to perform your request.');
 481:                 throw new Horde_Imsp_Exception('IMSP server is unable to perform your request.');
 482:             case 'BAD':
 483:                 $this->_imsp->_logger->err('The IMSP server did not understand your request.');
 484:                 throw new Horde_Imsp_Exception('The IMSP server did not understand your request.');
 485:             case 'OK':
 486:                 $this->_imsp->_logger->debug('STOREADDRESS Completed successfully.');
 487: 
 488:                 //we were successful...so release the lock on the entry
 489:                 $this->unlockEntry($abook, $entryInfo['name']);
 490:             }
 491:         }
 492:     }
 493: 
 494:     /**
 495:      * Deletes an abook entry.
 496:      *
 497:      * @param string $abook     Name of address book containing entry.
 498:      * @param string $bookEntry Name of entry to delete.
 499:      *
 500:      * @throws Horde_Imsp_Exception
 501:      */
 502:     public function deleteEntry($abook, $bookEntry)
 503:     {
 504:         // Start the command.
 505:         $this->_imsp->send('DELETEADDRESS ', true, false);
 506:         // Need {} for book name?
 507:         if (preg_match(Horde_Imsp_Client_Base::MUST_USE_LITERAL, $abook)) {
 508:             $biBook = sprintf("{%d}", strlen($abook));
 509:             $this->_imsp->send($biBook, false, true, true);
 510:         }
 511:         $this->_imsp->send("$abook ", false, false);
 512: 
 513:         //How bout for the entry name?
 514:         if (preg_match(Horde_Imsp_Client_Base::MUST_USE_LITERAL, $bookEntry)) {
 515:             $biEntry = sprintf("{%d}", strlen($bookEntry));
 516:             $this->_imsp->send($biEntry, false, true, true);
 517:         } else {
 518:             $bookEntry = $this->_imsp->quoteSpacedString($bookEntry);
 519:         }
 520:         $this->_imsp->send($bookEntry, false, true);
 521:         $server_response = $this->_imsp->receive();
 522:         switch ($server_response) {
 523:         case 'NO':
 524:             // Could not create abook.
 525:             $this->_imsp->_logger->err('IMSP server is unable to perform your request.');
 526:             throw new Horde_Imsp_Exception('IMSP server is unable to perform your request.');
 527:         case 'BAD':
 528:             $this->_imsp->_logger->err('The IMSP server did not understand your request.');
 529:             throw new Horde_Imsp_Exception('The IMSP server did not understand your request.');
 530:         case 'OK':
 531:             $this->_imsp->_logger->debug('DELETE Completed successfully.');
 532:         }
 533:     }
 534: 
 535:     /**
 536:      * Attempts to acquire a semaphore on the address book entry.
 537:      *
 538:      * @param string $abook     Address book name
 539:      * @param string $bookEntry Name of entry to lock
 540:      *
 541:      * @return mixed true or array on success (depends on server in use).
 542:      */
 543:     public function lockEntry($abook, $bookEntry)
 544:     {
 545:         $this->_imsp->send('LOCK ADDRESSBOOK ', true, false);
 546: 
 547:         // Do we need a string literal?
 548:         if (preg_match(Horde_Imsp_Client_Base::MUST_USE_LITERAL, $abook)) {
 549:             $biBook = sprintf("{%d}", strlen($abook));
 550:             $this->_imsp->send($biBook, false, true, true);
 551:         }
 552:         $this->_imsp->send("$abook ", false, false);
 553:         // What about the entry name?
 554:         if (preg_match(Horde_Imsp_Client_Base::MUST_USE_LITERAL, $bookEntry)) {
 555:             $biEntry = sprintf("{%d}", strlen($bookEntry));
 556:             $this->_imsp->send($biEntry, false, true, true);
 557:             $this->_imsp->send($bookEntry, false, true);
 558:         } else {
 559:             $bookEntry = $this->_imsp->quoteSpacedString($bookEntry);
 560:             $this->_imsp->send("$bookEntry", false, true);
 561:         }
 562: 
 563:         $server_response = $this->_imsp->receive();
 564:         do {
 565:             switch ($server_response) {
 566:             case 'NO':
 567:                 // Could not create abook.
 568:                 $this->_imsp->_logger->err('IMSP server is unable to perform your request.');
 569:                 throw new Horde_Imsp_Exception('IMSP server is unable to perform your request.');
 570:             case 'BAD':
 571:                 $this->_imsp->_logger->err('The IMSP server did not understand your request.');
 572:                 throw new Horde_Imsp_Exception('The IMSP server did not understand your request.');
 573:             }
 574: 
 575:             //Check to see if this is a FETCHADDRESS resonse
 576:             $dummy = $this->_parseFetchAddressResponse($server_response);
 577:             if ($dummy) {
 578:                 $server_response = $this->_imsp->receive();
 579:             }
 580:         } while ($server_response != 'OK');
 581: 
 582:         $this->_imsp->_logger->debug("LOCK ADDRESSBOOK on $abook $bookEntry OK");
 583: 
 584:         // Return either true or the FETCHADDRESS response if it exists.
 585:         if (!$dummy) {
 586:             return true;
 587:         } else {
 588:             return $dummy;
 589:         }
 590:     }
 591: 
 592:     /**
 593:      * Unlocks a previously locked address book.
 594:      *
 595:      * @param string $abook     Name of address book containing locked entry.
 596:      * @param string $bookEntry Name of entry to unlock.
 597:      *
 598:      * @throws Horde_Imsp_Exception
 599:      */
 600:     public function unlockEntry($abook, $bookEntry)
 601:     {
 602:         // Start sending command.
 603:         $this->_imsp->send('UNLOCK ADDRESSBOOK ', true, false);
 604: 
 605:         // {} for book name?
 606:         if (preg_match(Horde_Imsp_Client_Base::MUST_USE_LITERAL, $abook)) {
 607:             $biBook = sprintf("{%d}", strlen($abook));
 608:             $this->_imsp->send($biBook, false, true, true);
 609:         }
 610:         $this->_imsp->send("$abook ", false, false);
 611:         //How bout for entry name?
 612:         if (preg_match(Horde_Imsp_Client_Base::MUST_USE_LITERAL, $bookEntry)) {
 613:             $biEntry=sprintf("{%d}", strlen($bookEntry));
 614:             $this->_imsp->send($biEntry, false, true, true);
 615:             $this->_imsp->send($bookEntry, false, true);
 616:         } else {
 617:             $bookEntry = $this->_imsp->quoteSpacedString($bookEntry);
 618:             $this->_imsp->send("$bookEntry", false, true);
 619:         }
 620:         $response = $this->_imsp->receive();
 621:         switch ($response) {
 622:         case 'NO':
 623:             // Could not create abook.
 624:             $this->_imsp->_logger->err('IMSP server is unable to perform your request.');
 625:             throw new Horde_Imsp_Exception('IMSP server is unable to perform your request.');
 626:         case 'BAD':
 627:             $this->_imsp->_logger->err('The IMSP server did not understand your request.');
 628:             throw new Horde_Imsp_Exception('The IMSP server did not understand your request.');
 629:         case 'OK':
 630:             $this->_imsp->_logger->debug("UNLOCK ADDRESSBOOK on $abook $bookEntry OK");
 631:         }
 632:     }
 633: 
 634:     /**
 635:      * Access Control List (ACL)  Methods.
 636:      *
 637:      * The following characters are recognized ACL characters: lrwcda
 638:      * l - "lookup"  (see the name and existence of the address book)
 639:      * r - "read"    (search and retrieve addresses from address book)
 640:      * w - "write"   (create/edit new address book entries - not delete)
 641:      * c - "create"  (create new address books under the current address book)
 642:      * d - "delete"  (delete entries or entire book)
 643:      * a - "admin"   (set ACL lists for this address book - usually only
 644:      *               allowed for the owner of the address book)
 645:      *
 646:      * examples:
 647:      *  "lr" would be read only for that user
 648:      *  "lrw" would be read/write
 649:      */
 650: 
 651:     /**
 652:      * Sets an Access Control List for an abook.
 653:      *
 654:      * @param string $abook Name of address book.
 655:      * @param string $ident Name of user for this acl.
 656:      * @param string $acl   acl for this user/book.
 657:      *
 658:      * @return mixed True on success / PEAR_Error on failure.
 659:      */
 660:     public function setACL($abook, $ident, $acl)
 661:     {
 662:         // Verify that $acl looks good.
 663:         if (preg_match("/[^" . self::ACL_RIGHTS . "]/", $acl)) {
 664:             $this->_imsp->_logger('Bad Argument');
 665:             throw new InvalidArgumentException();
 666:         }
 667: 
 668:         // Begin sending command.
 669:         $this->_imsp->send('SETACL ADDRESSBOOK ', true, false);
 670:         // {} for book name?
 671:         if (preg_match(Horde_Imsp_Client_Base::MUST_USE_LITERAL, $abook)) {
 672:             $biBook = sprintf("{%d}", strlen($abook));
 673:             $this->_imsp->send($biBook, false, true, true);
 674:         }
 675:         $this->_imsp->send("$abook ", false, false);
 676: 
 677:         // {} for ident?
 678:         if (preg_match(Horde_Imsp_Client_Base::MUST_USE_LITERAL, $ident)) {
 679:             $biIdent = sprintf("{%d}", strlen($ident));
 680:             $this->_imsp->send($biIdent, false, true, true);
 681:         }
 682:         $this->_imsp->send("$ident ", false, false);
 683: 
 684:         // Now finish up with the actual ACL.
 685:         $this->_imsp->send($acl, false, true);
 686:         $response = $this->_imsp->receive();
 687:         switch ($response) {
 688:         case 'NO':
 689:             // Could not create abook.
 690:             $this->_imsp->_logger->err('IMSP server is unable to perform your request.');
 691:             throw new Horde_Imsp_Exception('IMSP server is unable to perform your request.');
 692:         case 'BAD':
 693:             $this->_imsp->_logger->err('The IMSP server did not understand your request.');
 694:             throw new Horde_Imsp_Exception('The IMSP server did not understand your request.');
 695:         case 'OK':
 696:             $this->_imsp->_logger->debug("ACL set for $ident on $abook");
 697:             break;
 698:         default:
 699:             // Do not know why we would make it down here.
 700:             $this->_imsp->_logger->err('Did not receive the expected response from the server.');
 701:             throw new Horde_Imsp_Exception('Did not receive the expected response from the server.');
 702:         }
 703:     }
 704: 
 705:     /**
 706:      * Retrieves an address book's ACL.
 707:      *
 708:      * @param string $abook Name of address book to retrieve acl for.
 709:      *
 710:      * @return mixed array containing acl for every user with access to
 711:      *                     address book or PEAR_Error on failure.
 712:      */
 713:     public function getACL($abook)
 714:     {
 715:         $this->_imsp->send('GETACL ADDRESSBOOK ', true, false);
 716: 
 717:         // {} for book name?
 718:         if (preg_match(Horde_Imsp_Client_Base::MUST_USE_LITERAL, $abook)) {
 719:             $biName = sprintf("{%d}", strlen($abook));
 720:             $this->_imsp->send($biName, false, true, true);
 721:         }
 722:         $this->_imsp->send($abook, false, true);
 723: 
 724:         // Get results.
 725:         $response = $this->_imsp->receive();
 726:         switch ($response) {
 727:         case 'NO':
 728:             // Could not create abook.
 729:             $this->_imsp->_logger->err('IMSP server is unable to perform your request.');
 730:             throw new Horde_Imsp_Exception('IMSP server is unable to perform your request.');
 731:         case 'BAD':
 732:             $this->_imsp->_logger->err('The IMSP server did not understand your request.');
 733:             throw new Horde_Imsp_Exception('The IMSP server did not understand your request.');
 734:         }
 735: 
 736:         // If we are here, we need to receive the * ACL Responses.
 737:         do {
 738:             /* Get an array of responses.
 739:              * The [3] element should be the address book name
 740:              * [4] and [5] will be user/group name and permissions */
 741: 
 742:             //the book name might be a literal
 743:             if (preg_match(Horde_Imsp_Client_Base::OCTET_COUNT, $response, $tempArray)) {
 744:                 $data = $this->_imsp->receiveStringLiteral($tempArray[2]);
 745:                 $response = $this->_imsp->receive();
 746:             }
 747: 
 748:             $parts = explode(' ', $response);
 749: 
 750:             // Push the array if book was a literal
 751:             if ($data) {
 752:                 array_unshift($parts, ' ', ' ', ' ', ' ');
 753:             }
 754:             // Address book name quoted?
 755:             $numParts = count($parts);
 756:             $name = $parts[3];
 757:             $firstACLIdx = 4;
 758:             $firstChar = substr($name, 0, 1);
 759:             if ($firstChar == "\"") {
 760:                 for ($i = 4; $i < $numParts; $i++) {
 761:                     $lastChar = substr($parts[$i], strlen($parts[$i]) - 1, 1);
 762:                     $firstACLIdx++;
 763:                     if ($lastChar == "\"") {
 764:                         break;
 765:                     }
 766:                 }
 767:             }
 768: 
 769:             for ($i = $firstACLIdx; $i < count($parts); $i += 2) {
 770:                 $results[$parts[$i]] = $parts[$i+1];
 771:             }
 772: 
 773:             $response = $this->_imsp->receive();
 774: 
 775:         } while (preg_match("/^\* ACL ADDRESSBOOK/", $response));
 776: 
 777:         // Hopefully we can receive an OK response here
 778:         if ($response != 'OK') {
 779:             // Some weird problem
 780:             throw new Horde_Imsp_Exception('Did not receive the expected response from the server.');
 781:         }
 782:         $this->_imsp->_logger->debug("GETACL on $abook completed.");
 783: 
 784:         return $results;
 785:     }
 786: 
 787:     /**
 788:      * Deletes an ACL entry for an address book.
 789:      *
 790:      * @param string $abook Name of the address book.
 791:      * @param string $ident Name of entry to remove acl for.
 792:      *
 793:      * @throws Horde_Imsp_Exception
 794:      */
 795:     function deleteACL($abook, $ident)
 796:     {
 797:         $this->_imsp->send('DELETEACL ADDRESSBOOK ', true, false);
 798: 
 799:         // Do we need literal for address book name?
 800:         if (preg_match(Horde_Imsp_Client_Base::MUST_USE_LITERAL, $abook)) {
 801:             $biBook = sprintf("{%d}", strlen($abook));
 802:             $this->_imsp->send($biBook, false, true, true);
 803:         }
 804:         $this->_imsp->send("$abook ", false, false);
 805: 
 806:         // Literal for ident name?
 807:         if (preg_match(Horde_Imsp_Client_Base::MUST_USE_LITERAL, $ident)) {
 808:             $biIdent = sprintf("{%d}", strlen($ident));
 809:             $this->_imsp->send($biIdent, false, true, true);
 810:             $this->_imsp->send($ident, false, true);
 811:         } else {
 812:             $this->_imsp->send("\"$ident\"", false, true);
 813:         }
 814: 
 815:         // Get results.
 816:         $server_response = $this->_imsp->receive();
 817:         switch ($server_response) {
 818:         case 'NO':
 819:             // Could not create abook.
 820:             $this->_imsp->_logger->err('IMSP server is unable to perform your request.');
 821:             throw new Horde_Imsp_Exception('IMSP server is unable to perform your request.');
 822:         case 'BAD':
 823:             $this->_imsp->_logger->err('The IMSP server did not understand your request.');
 824:             throw new Horde_Imsp_Exception('The IMSP server did not understand your request.');
 825:         case 'OK':
 826:             $this->_imsp->_logger->debug("DELETED ACL for $ident on $abook");
 827:         default:
 828:             throw new Horde_Imsp_Exception('Did not receive the expected response from the server.');
 829:         }
 830:     }
 831: 
 832:     /**
 833:      * Returns an ACL string containing the rights for the current user
 834:      *
 835:      * @param string $abook Name of address book to retrieve acl.
 836:      *
 837:      * @return mixed acl of current user.
 838:      */
 839:     public function myRights($abook)
 840:     {
 841:         $data = '';
 842:         $this->_imsp->send('MYRIGHTS ADDRESSBOOK ', true, false);
 843:         if (preg_match(Horde_Imsp_Client_Base::MUST_USE_LITERAL, $abook)) {
 844:             $biBook = sprintf("{%d}", strlen($abook));
 845:             $this->_imsp->send($biBook, false, true, true);
 846:         }
 847:         $this->_imsp->send($abook, false, true);
 848:         $server_response = $this->_imsp->receive();
 849:         switch ($server_response) {
 850:         case 'NO':
 851:             // Could not create abook.
 852:             $this->_imsp->_logger->err('IMSP server is unable to perform your request.');
 853:             throw new Horde_Imsp_Exception('IMSP server is unable to perform your request.');
 854:         case 'BAD':
 855:             $this->_imsp->_logger->err('The IMSP server did not understand your request.');
 856:             throw new Horde_Imsp_Exception('The IMSP server did not understand your request.');
 857:         }
 858: 
 859:         if (!preg_match("/^\* MYRIGHTS ADDRESSBOOK/", $server_response)) {
 860:             throw new Horde_Imsp_Exception('Did not receive the expected response from the server.');
 861:         }
 862: 
 863:         // {} for the abook name?
 864:         if (preg_match(Horde_Imsp_Client_Base::OCTET_COUNT, $server_response, $tempArray)) {
 865:             $data = $this->_imsp->receiveStringLiteral($tempArray[2]);
 866:             $server_response = $this->_imsp->receive();
 867:         }
 868: 
 869:         $parts = explode(' ', $server_response);
 870: 
 871:         // Push the array if we had a {}
 872:         if ($data) {
 873:             array_unshift($parts, ' ', ' ', ' ', ' ');
 874:         }
 875: 
 876:         // Quoted address book name?
 877:         $numParts = count($parts);
 878:         $name = $parts[3];
 879:         $firstACLIdx = 4;
 880:         $firstChar = substr($name, 0, 1);
 881:         if ($firstChar == "\"") {
 882:             for ($i = 4; $i < $numParts; $i++) {
 883:                 $lastChar = substr($parts[$i], strlen($parts[$i]) - 1, 1);
 884:                 $firstACLIdx++;
 885:                 if ($lastChar == "\"") {
 886:                     break;
 887:                 }
 888:             }
 889:         }
 890: 
 891:         $acl = $parts[$firstACLIdx];
 892:         $server_response = $this->_imsp->receive();
 893: 
 894:         if ($server_response != 'OK') {
 895:             throw new Horde_Imsp_Exception('Did not receive the expected response from the server.');
 896:         } else {
 897:             $this->_imsp->_logger->debug("MYRIGHTS on $abook completed.");
 898:             return $acl;
 899:         }
 900:     }
 901: 
 902:     /**
 903:      * Parses a IMSP fetchaddress response text string into key-value pairs
 904:      *
 905:      * @param  string  $server_response The raw fetchaddress response.
 906:      *
 907:      * @return array   Address book entry information as key=>value pairs.
 908:      */
 909:     protected function _parseFetchAddressResponse($server_response)
 910:     {
 911:         $abook = '';
 912:         if (!preg_match("/^\* FETCHADDRESS /", $server_response)) {
 913:             $this->_imsp->_logger->err('Did not receive a FETCHADDRESS response from server.');
 914:             throw new Horde_Imsp_Exception('Did not receive the expected response from the server.');
 915:         }
 916: 
 917:         /* NOTES
 918:          * Parse out the server response string
 919:          *
 920:          * After choping off the server command response tags and
 921:          * explode()'ing the server_response string
 922:          * the $parts array contains the chunks of the server returned data.
 923:          *
 924:          * The predifined 'name' field starts in $parts[1].
 925:          * The server should return any single item of data
 926:          * that contains spaces within it as a double quoted string.
 927:          * So we can interpret the existence of a double quote at the beginning
 928:          * of a chunk to mean that the next chunk(s) are part of
 929:          * the same value.  A double quote at the end of a chunk signifies the
 930:          * end of that value and the chunk following that can be interpreted
 931:          * as a key name.
 932:          *
 933:          * We also need to watch for the server returning a {} response for the
 934:          * value of the key as well. */
 935: 
 936:         // Was the address book name a  {}?
 937:         if (preg_match("/(^\* FETCHADDRESS )({)([0-9]{1,})(\}$)/",
 938:                        $server_response, $tempArray)) {
 939:             $abook = $this->_imsp->receiveStringLiteral($tempArray[3]);
 940:             $chopped_response = trim($this->_imsp->receive());
 941:         } else {
 942:             // Take off the stuff from the beginning of the response
 943:             $chopped_response = trim(preg_replace("/^\* FETCHADDRESS /", '', $server_response));
 944:         }
 945: 
 946:         $parts = explode(' ', $chopped_response);
 947:         /* If addres book was sent as a {} then we must 'push' a blank
 948:          * value to the start of this array so the rest of the routine
 949:          * will work with the correct indexes. */
 950:          if (!empty($abook)) {
 951:             array_unshift($parts, ' ');
 952:         }
 953: 
 954:         // Was the address book name quoted?
 955:         $numOfParts = count($parts);
 956:         $name = $parts[0];
 957:         $firstNameIdx = 1;
 958:         $firstChar = substr($name, 0, 1);
 959:         if ($firstChar =="\"") {
 960:             for ($i = 1; $i < $numOfParts; $i++) {
 961:                 $lastChar = substr($parts[$i], strlen($parts[$i]) - 1, 1);
 962:                 $firstNameIdx++;
 963:                 if ($lastChar == "\"") {
 964:                     break;
 965:                 }
 966:             }
 967:         }
 968: 
 969:         // Now start working on the entry name
 970:         $name = $parts[$firstNameIdx];
 971:         $firstChar = substr($name,0,1);
 972: 
 973:         // Check to see if the first char of the name string is a double quote
 974:         // so we know if we have to extract more of the name.
 975:         if ($firstChar == "\"") {
 976:             $name = ltrim($name, "\"");
 977:             for ($i = $firstNameIdx + 1; $i < $numOfParts; $i++) {
 978:                 $name .=  ' ' . $parts[$i];
 979:                 $lastChar = substr($parts[$i], strlen($parts[$i]) - 1,1);
 980:                 if ($lastChar == "\"") {
 981:                     $name = rtrim($name, "\"");
 982:                     $nextKey = $i + 1;
 983:                     break;
 984:                 }
 985:             }
 986: 
 987:         // Check for {}
 988:         } elseif (preg_match('/\{(\d+)\}/', $name, $matches)) {
 989:             $name = $this->_imsp->receiveStringLiteral($matches[1]);
 990:             $response=$this->_imsp->receive();
 991:             $parts = explode(' ', $response);
 992:             $numOfParts = count($parts);
 993:             $nextKey = 0;
 994:         } else {
 995:             // If only one chunk for 'name' then we just have to point
 996:             // to the next chunk in the array...which will hopefully
 997:             // be '2'
 998:             $nextKey = $firstNameIdx + 1;
 999:         }
1000: 
1001:         $lastChar = '';
1002:         $entry['name'] = $name;
1003: 
1004:         // Start parsing the rest of the response.
1005:         for ($i = $nextKey; $i < $numOfParts; $i += 2) {
1006:             $key = $parts[$i];
1007:             /* Check for {} */
1008:             if (@preg_match(Horde_Imsp_Client_Base::OCTET_COUNT, $parts[$i+1], $tempArray)) {
1009:                 $server_data = $this->_imsp->receiveStringLiteral($tempArray[2]);
1010:                 $entry[$key] = $server_data;
1011: 
1012:                 /* Read any remaining data from the stream and reset
1013:                  * the counter variables so the loop will continue
1014:                  * correctly. Note we set $i  to -2 because it will
1015:                  * be incremented by 2 before the loop will run again */
1016:                 $parts = $this->_imsp->getServerResponseChunks();
1017:                 $i = -2;
1018:                 $numOfParts = count($parts);
1019:             } else {
1020:                 // Not a string literal response
1021:                 @$entry[$key] = $parts[$i + 1];
1022:                  // Check to see if the value started with a double
1023:                  // quote.  We also need to check if the last char is a
1024:                  // quote to make sure we REALLY have to check the next
1025:                  // elements for a closing quote.
1026:                 if ((@substr($parts[$i + 1], 0, 1) == '"') &&
1027:                     (substr($parts[$i + 1],
1028:                      strlen($parts[$i + 1]) - 1, 1) != '"')) {
1029: 
1030:                     do {
1031:                         $nextElement = $parts[$i+2];
1032: 
1033:                         // Was this element the last one?
1034:                         $lastChar = substr($nextElement, strlen($nextElement) - 1, 1);
1035:                         $entry[$key] .= ' ' . $nextElement;
1036: 
1037:                         // NOW, we can check the lastChar.
1038:                         if ($lastChar == '"') {
1039:                             $done = true;
1040:                             $i++;
1041:                         } else {
1042:                             // Check to see if the next element is the
1043:                             // last one. If so, the do loop will terminate.
1044:                             $done = false;
1045:                             $lastChar = substr($parts[$i+3], strlen($parts[$i+3]) - 1,1);
1046:                             $i++;
1047:                         }
1048:                     } while ($lastChar != '"');
1049: 
1050:                     // Do we need to add the final element, or were
1051:                     // there only two total?
1052:                     if (!$done) {
1053:                         $nextElement = $parts[$i+2];
1054:                         $entry[$key] .= ' ' . $nextElement;
1055:                         $i++;
1056:                     }
1057: 
1058:                     // Remove the quotes sent back to us from the server.
1059:                     if (substr($entry[$key], 0, 1) == '"') {
1060:                         $entry[$key] = substr($entry[$key], 1, strlen($entry[$key]) - 2);
1061:                     }
1062: 
1063:                     if (substr($entry[$key], strlen($entry[$key]) - 1, 1) == '"') {
1064:                         $entry[$key] = substr($entry[$key], 0, strlen($entry[$key]) - 2);
1065:                     }
1066:                 } elseif ((@substr($parts[$i + 1], 0, 1) == '"') &&
1067:                           (substr($parts[$i + 1], -1, 1) == '"')) {
1068:                     // Remove the quotes sent back to us from the server.
1069:                     if (substr($entry[$key], 0, 1) == '"') {
1070:                         $entry[$key] = substr($entry[$key], 1, strlen($entry[$key]) - 2);
1071:                     }
1072: 
1073:                     if (substr($entry[$key], -1, 1) == '"') {
1074:                         $entry[$key] = substr($entry[$key], 0, strlen($entry[$key]) - 2);
1075:                     }
1076:                 }
1077:             }
1078:         }
1079: 
1080:         return $entry;
1081:     }
1082: 
1083: }
1084: 
API documentation generated by ApiGen