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 directory driver implementation for an IMSP server.
  4:  *
  5:  * Copyright 2010-2012 Horde LLC (http://www.horde.org/)
  6:  *
  7:  * See the enclosed file LICENSE for license information (ASL).  If you did
  8:  * did not receive this file, see http://www.horde.org/licenses/apache.
  9:  *
 10:  * @author   Michael Rubinsky <mrubinsk@horde.org>
 11:  * @category Horde
 12:  * @license  http://www.horde.org/licenses/apache ASL
 13:  * @package  Turba
 14:  */
 15: class Turba_Driver_Imsp extends Turba_Driver
 16: {
 17:     /**
 18:      * Horde_Imsp object
 19:      *
 20:      * @var Horde_Imsp
 21:      */
 22:     protected $_imsp;
 23: 
 24:     /**
 25:      * The name of the addressbook.
 26:      *
 27:      * @var string
 28:      */
 29:     protected $_bookName  = '';
 30: 
 31:     /**
 32:      * Holds if we are authenticated.
 33:      *
 34:      * @var boolean
 35:      */
 36:     protected $_authenticated = '';
 37: 
 38:     /**
 39:      * Holds name of the field indicating an IMSP group.
 40:      *
 41:      * @var string
 42:      */
 43:     protected $_groupField = '';
 44: 
 45:     /**
 46:      * Holds value that $_groupField will have if entry is an IMSP group.
 47:      *
 48:      * @var string
 49:      */
 50:     protected $_groupValue = '';
 51: 
 52:     /**
 53:      * Used to set if the current search is for contacts only.
 54:      *
 55:      * @var boolean
 56:      */
 57:     protected $_noGroups = '';
 58: 
 59:     /**
 60:      * Driver capabilities.
 61:      *
 62:      * @var array
 63:      */
 64:     protected $_capabilities = array(
 65:         'delete_all' => true,
 66:         'delete_addressbook' => true
 67:     );
 68: 
 69:     /**
 70:      * Constructs a new Turba imsp driver object.
 71:      *
 72:      * @param array $params  Hash containing additional configuration
 73:      *                       parameters.
 74:      */
 75:     public function __construct($name = '', $params)
 76:     {
 77:         global $conf;
 78:         parent::__construct($name, $params);
 79: 
 80:         $this->params       = $params;
 81:         $this->_groupField  = $params['group_id_field'];
 82:         $this->_groupValue  = $params['group_id_value'];
 83:         $this->_myRights    = $params['my_rights'];
 84:         $this->_perms       = $this->_aclToHordePerms($params['my_rights']);
 85:         $this->_bookName = $this->getContactOwner();
 86: 
 87:         try {
 88:             $this->_imsp = $GLOBALS['injector']
 89:                 ->getInstance('Horde_Core_Factory_Imsp')
 90:                 ->create('Book', $this->params);
 91:         } catch (Horde_Exception $e) {
 92:             $this->_authenticated = false;
 93:             throw new Turba_Exception($e);
 94:         }
 95:         $this->_authenticated = true;
 96:     }
 97: 
 98:     /**
 99:      * Returns all entries matching $critera.
100:      *
101:      * @param array $criteria  Array containing the search criteria.
102:      * @param array $fields    List of fields to return.
103:      *
104:      * @return array  Hash containing the search results.
105:      */
106:     protected function _search(array $criteria, array $fields, array $blobFields = array(), $count_only)
107:     {
108:         $query = $results = array();
109: 
110:         if (!$this->_authenticated) {
111:             return $query;
112:         }
113: 
114:         /* Get the search criteria. */
115:         if (count($criteria)) {
116:             foreach ($criteria as $key => $vals) {
117:                 $names = (strval($key) == 'OR')
118:                     ? $this->_doSearch($vals, 'OR')
119:                     : $this->_doSearch($vals, 'AND');
120:             }
121:         }
122: 
123:         /* Now we have a list of names, get the rest. */
124:         $result = $this->_read('name', $names, null, $fields);
125:         if (is_array($result)) {
126:             $results = $result;
127:         }
128: 
129:         Horde::logMessage(sprintf('IMSP returned %s results', count($results)), 'DEBUG');
130: 
131:         return $count_only ? count($results) : array_values($results);
132:     }
133: 
134:     /**
135:      * Reads the given data from the address book and returns the results.
136:      *
137:      * @param string $key        The primary key field to use (always 'name'
138:      *                           for IMSP).
139:      * @param mixed $ids         The ids of the contacts to load.
140:      * @param string $owner      Only return contacts owned by this user.
141:      * @param array $fields      List of fields to return.
142:      * @param array $blobFields  Array of fields containing binary data.
143:      *
144:      * @return array  Hash containing the search results.
145:      * @throws Turba_Exception
146:      */
147:     protected function _read($key, $ids, $owner, array $fields,
148:                              array $blobFields = array())
149:     {
150:         $results = array();
151: 
152:         if (!$this->_authenticated) {
153:             return $results;
154:         }
155: 
156:         $ids = array_values($ids);
157:         $idCount = count($ids);
158:         $IMSPGroups = $members = $tmembers = array();
159: 
160:         for ($i = 0; $i < $idCount; ++$i) {
161:             $result = array();
162: 
163:             try {
164:                 $temp = isset($IMSPGroups[$ids[$i]])
165:                     ? $IMSPGroups[$ids[$i]]
166:                     : $this->_imsp->getEntry($this->_bookName, $ids[$i]);
167:             } catch (Horde_Imsp_Exception $e) {
168:                 continue;
169:             }
170: 
171:             $temp['fullname'] = $temp['name'];
172:             $isIMSPGroup = false;
173:             if (!isset($temp['__owner'])) {
174:                 $temp['__owner'] = $GLOBALS['registry']->getAuth();
175:             }
176: 
177:             if ((isset($temp[$this->_groupField])) &&
178:                 ($temp[$this->_groupField] == $this->_groupValue)) {
179:                 if ($this->_noGroups) {
180:                     continue;
181:                 }
182:                 if (!isset($IMSPGroups[$ids[$i]])) {
183:                     $IMSPGroups[$ids[$i]] = $temp;
184:                 }
185:                 // move group ids to end of list
186:                 if ($idCount > count($IMSPGroups) &&
187:                     $idCount - count($IMSPGroups) > $i) {
188:                     $ids[] = $ids[$i];
189:                     unset($ids[$i]);
190:                     $ids = array_values($ids);
191:                     --$i;
192:                     continue;
193:                 }
194:                 $isIMSPGroup = true;
195:             }
196:             // Get the group members that might have been added from other
197:             // IMSP applications, but only if we need more information than
198:             // the group name
199:             if ($isIMSPGroup &&
200:                 array_search('__members', $fields) !== false) {
201:                 if (isset($temp['email'])) {
202:                     $emailList = $this->_getGroupEmails($temp['email']);
203:                     $count = count($emailList);
204:                     for ($j = 0; $j < $count; ++$j) {
205:                         $needMember = true;
206:                         foreach ($results as $curResult) {
207:                             if (!empty($curResult['email']) &&
208:                                 strtolower($emailList[$j]) == strtolower(trim($curResult['email']))) {
209:                                 $members[] = $curResult['name'];
210:                                 $needMember = false;
211:                             }
212:                         }
213:                         if ($needMember) {
214:                             $memberName = $this->_imsp->search
215:                                 ($this->_bookName,
216:                                  array('email' => trim($emailList[$j])));
217: 
218:                             if (count($memberName)) {
219:                                 $members[] = $memberName[0];
220:                             }
221:                         }
222:                     }
223:                 }
224:                 if (!empty($temp['__members'])) {
225:                     $tmembers = @unserialize($temp['__members']);
226:                 }
227: 
228:                 // TODO: Make sure that we are using the correct naming
229:                 // convention for members regardless of if we are using
230:                 // shares or not. This is needed to assure groups created
231:                 // while not using shares won't be lost when transitioning
232:                 // to shares and visa versa.
233:                 //$tmembers = $this->_checkMemberFormat($tmembers);
234: 
235:                 $temp['__members'] = serialize($this->_removeDuplicated(
236:                                                array($members, $tmembers)));
237:                 $temp['__type'] = 'Group';
238:                 $temp['email'] = null;
239:                 $result = $temp;
240:             } else {
241:                 // IMSP contact.
242:                 $count = count($fields);
243:                 for ($j = 0; $j < $count; ++$j) {
244:                     if (isset($temp[$fields[$j]])) {
245:                         $result[$fields[$j]] = $temp[$fields[$j]];
246:                     }
247:                 }
248:             }
249: 
250:             $results[] = $result;
251:         }
252: 
253:         return $results;
254:     }
255: 
256:     /**
257:      * Adds the specified contact to the addressbook.
258:      *
259:      * @param array $attributes  The attribute values of the contact.
260:      * @param array $blob_fields TODO
261:      *
262:      * @throws Turba_Exception
263:      */
264:     protected function _add(array $attributes, array $blob_fields = array())
265:     {
266:         /* We need to map out Turba_Object_Groups back to IMSP groups before
267:          * writing out to the server. We need to array_values() it in
268:          * case an entry was deleted from the group. */
269:         if ($attributes['__type'] == 'Group') {
270:             /* We may have a newly created group. */
271:             $attributes[$this->_groupField] = $this->_groupValue;
272:             if (!isset($attributes['__members'])) {
273:                 $attributes['__members'] = '';
274:                 $attributes['email'] = ' ';
275:             }
276:             $temp = unserialize($attributes['__members']);
277:             if (is_array($temp)) {
278:                 $members = array_values($temp);
279:             } else {
280:                 $members = array();
281:             }
282: 
283:             // This searches the current IMSP address book to see if
284:             // we have a match for this member before adding to email
285:             // attribute since IMSP groups in other IMSP aware apps
286:             // generally require an existing conact entry in the current
287:             // address book for each group member (this is necessary for
288:             // those sources that may be used both in AND out of Horde).
289:             try {
290:                 $result = $this->_read('name', $members, null, array('email'));
291:                 $count = count($result);
292:                 for ($i = 0; $i < $count; ++$i) {
293:                     if (isset($result[$i]['email'])) {
294:                         $contact = sprintf("%s<%s>\n", $members[$i],
295:                                            $result[$i]['email']);
296:                         $attributes['email'] .= $contact;
297:                     }
298:                 }
299:             } catch (Turba_Exception $e) {}
300:         }
301: 
302:         unset($attributes['__type'], $attributes['fullname']);
303:         if (!$this->params['contact_ownership']) {
304:             unset($attributes['__owner']);
305:         }
306: 
307:         return $this->_imsp->addEntry($this->_bookName, $attributes);
308:     }
309: 
310:     /**
311:      * TODO
312:      */
313:     protected function _canAdd()
314:     {
315:         return true;
316:     }
317: 
318:     /**
319:      * Deletes the specified object from the IMSP server.
320:      *
321:      * @throws Turba_Exception
322:      */
323:     protected function _delete($object_key, $object_id)
324:     {
325:         try {
326:             $this->_imsp->deleteEntry($this->_bookName, $object_id);
327:         } catch (Horde_Imsp_Exception $e) {
328:             throw new Turba_Exception($e);
329:         }
330:     }
331: 
332:     /**
333:      * Deletes the address book represented by this driver from the IMSP server.
334:      *
335:      * @throws Turba_Exception
336:      */
337:      protected function _deleteAll()
338:      {
339:          try {
340:              $this->_imsp->deleteAddressbook($this->_bookName);
341:          } catch (Horde_Imsp_Exception $e) {
342:              throw new Turba_Exception($e);
343:          }
344:      }
345: 
346:     /**
347:      * Saves the specified object to the IMSP server.
348:      *
349:      * @param Turba_Object $object  The object to save/update.
350:      *
351:      * @return string  The object id, possibly updated.
352:      * @throws Turba_Exception
353:      */
354:     protected function _save($object)
355:     {
356:         list($object_key, $object_id) = each($this->toDriverKeys(array('__key' => $object->getValue('__key'))));
357:         $attributes = $this->toDriverKeys($object->getAttributes());
358: 
359:         /* Check if the key changed, because IMSP will just write out
360:          * a new entry without removing the previous one. */
361:         if ($attributes['name'] != $this->_makeKey($attributes)) {
362:             $this->_delete($object_key, $attributes['name']);
363:             $attributes['name'] = $this->_makeKey($attributes);
364:             $object_id = $attributes['name'];
365:         }
366: 
367:         $this->_add($attributes);
368: 
369:         return $object_id;
370:     }
371: 
372:     /**
373:      * Create an object key for a new object.
374:      *
375:      * @param array $attributes  The attributes (in driver keys) of the
376:      *                           object being added.
377:      *
378:      * @return string  A unique ID for the new object.
379:      */
380:     protected function _makeKey($attributes)
381:     {
382:         return $attributes['fullname'];
383:     }
384: 
385:     /**
386:      * Parses out $emailText into an array of pure email addresses
387:      * suitable for searching the IMSP datastore with.
388:      *
389:      * @param string $emailText  Single string containing email addressses.
390:      *
391:      * @return array  Pure email address.
392:      */
393:     protected function _getGroupEmails($emailText)
394:     {
395:         preg_match_all("(\w[-._\w]*\w@\w[-._\w]*\w\.\w{2,3})", $emailText, $matches);
396:         return $matches[0];
397:     }
398: 
399:     /**
400:      * Parses the search criteria, requests the individual searches from the
401:      * server and performs any necessary ANDs / ORs on the results.
402:      *
403:      * @param array  $criteria  Array containing the search criteria.
404:      * @param string $glue      Type of search to perform (AND / OR).
405:      *
406:      * @return array  Array containing contact names that match $criteria.
407:      */
408:     protected function _doSearch($criteria, $glue)
409:     {
410:         $results = array();
411:         $names = array();
412:         foreach ($criteria as $key => $vals) {
413:             if (!empty($vals['OR'])) {
414:                 $results[] = $this->_doSearch($vals['OR'], 'OR');
415:             } elseif (!empty($vals['AND'])) {
416:                 $results[] = $this->_doSearch($vals['AND'], 'AND');
417:             } else {
418:                 /* If we are here, and we have a ['field'] then we
419:                  * must either do the 'AND' or the 'OR' search. */
420:                 if (isset($vals['field'])) {
421:                     $results[] = $this->_sendSearch($vals);
422:                 } else {
423:                     foreach ($vals as $test) {
424:                         if (!empty($test['OR'])) {
425:                             $results[] = $this->_doSearch($test['OR'], 'OR');
426:                         } elseif (!empty($test['AND'])) {
427:                             $results[] = $this->_doSearch($test['AND'], 'AND');
428:                         } else {
429:                             $results[] = $this->_doSearch(array($test), $glue);
430:                         }
431:                     }
432:                 }
433:             }
434:         }
435: 
436:         return ($glue == 'AND')
437:             ? $this->_getDuplicated($results)
438:             : $this->_removeDuplicated($results);
439:     }
440: 
441:     /**
442:      * Sends a search request to the server.
443:      *
444:      * @param array $criteria  Array containing the search critera.
445:      *
446:      * @return array  Array containing a list of names that match the search.
447:      */
448:     function _sendSearch($criteria)
449:     {
450:         global $conf;
451: 
452:         $names = '';
453:         $imspSearch = array();
454:         $searchkey = $criteria['field'];
455:         $searchval = $criteria['test'];
456:         $searchop = $criteria['op'];
457:         $hasName = false;
458:         $this->_noGroups = false;
459:         $cache = $GLOBALS['injector']->getInstance('Horde_Cache');
460:         $key = implode(".", array_merge($criteria, array($this->_bookName)));
461: 
462:         /* Now make sure we aren't searching on a dynamically created
463:          * field. */
464:         switch ($searchkey) {
465:         case 'fullname':
466:             if (!$hasName) {
467:                 $searchkey = 'name';
468:                 $hasName = true;
469:             } else {
470:                 $searchkey = '';
471:             }
472:             break;
473: 
474:         case '__owner':
475:             if (!$this->params['contact_ownership']) {
476:                 $searchkey = '';
477:                 $hasName = true;
478:             }
479:             break;
480:         }
481: 
482:         /* Are we searching for only Turba_Object_Groups or Turba_Objects?
483:          * This is needed so the 'Show Lists' and 'Show Contacts'
484:          * links work correctly in Turba. */
485:         if ($searchkey == '__type') {
486:             switch ($searchval) {
487:             case 'Group':
488:                 $searchkey = $this->_groupField;
489:                 $searchval = $this->_groupValue;
490:                 break;
491: 
492:             case 'Object':
493:                 if (!$hasName) {
494:                     $searchkey = 'name';
495:                     $searchval = '';
496:                     $hasName = true;
497:                 } else {
498:                     $searchkey = '';
499:                 }
500:                 $this->_noGroups = true;
501:                 break;
502:             }
503:         }
504: 
505:         if (!$searchkey == '') {
506:             // Check $searchval for content and for strict matches.
507:             if (strlen($searchval) > 0) {
508:                 if ($searchop == 'LIKE') {
509:                     $searchval = '*' . $searchval . '*';
510:                 }
511:             } else {
512:                 $searchval = '*';
513:             }
514:             $imspSearch[$searchkey] = $searchval;
515:         }
516:         if (!count($imspSearch)) {
517:             $imspSearch['name'] = '*';
518:         }
519: 
520:         /* Finally get to the command.  Check the cache first, since each
521:          * 'Turba' search may consist of a number of identical IMSP
522:          * searchaddress calls in order for the AND and OR parts to work
523:          * correctly.  15 Second lifetime should be reasonable for this. This
524:          * should reduce load on IMSP server somewhat.*/
525:         $results = $cache->get($key, 15);
526: 
527:         if ($results) {
528:             $names = unserialize($results);
529:         }
530: 
531:         if (!$names) {
532:             try {
533:                 $names = $this->_imsp->search($this->_bookName, $imspSearch);
534:                 $cache->set($key, serialize($names));
535:                 return $names;
536:             } catch (Horde_Imsp_Exception $e) {
537:                 $GLOBALS['notification']->push($names, 'horde.error');
538:             }
539:         } else {
540:             return $names;
541:         }
542:     }
543: 
544:     /**
545:      * Returns only those names that are duplicated in $names
546:      *
547:      * @param array $names  A nested array of arrays containing names
548:      *
549:      * @return array  Array containing the 'AND' of all arrays in $names
550:      */
551:     protected function _getDuplicated($names)
552:     {
553:         $matched = $results = array();
554: 
555:         /* If there is only 1 array, simply return it. */
556:         if (count($names) < 2) {
557:             return $names[0];
558:         }
559: 
560:         for ($i = 0; $i < count($names); ++$i) {
561:             if (is_array($names[$i])) {
562:                 $results = array_merge($results, $names[$i]);
563:             }
564:         }
565: 
566:         $search = array_count_values($results);
567:         foreach ($search as $key => $value) {
568:             if ($value > 1) {
569:                 $matched[] = $key;
570:             }
571:         }
572: 
573:         return $matched;
574:     }
575: 
576:     /**
577:      * Returns an array with all duplicate names removed.
578:      *
579:      * @param array $names  Nested array of arrays containing names.
580:      *
581:      * @return array  Array containg the 'OR' of all arrays in $names.
582:      */
583:     protected function _removeDuplicated($names)
584:     {
585:         $unames = array();
586:         for ($i = 0; $i < count($names); ++$i) {
587:             if (is_array($names[$i])) {
588:                 $unames = array_merge($unames, $names[$i]);
589:             }
590:         }
591: 
592:         return array_unique($unames);
593:     }
594: 
595:     /**
596:      * Checks if the current user has the requested permission
597:      * on this source.
598:      *
599:      * @param integer $perm  The permission to check for.
600:      *
601:      * @return boolean  true if user has permission, false otherwise.
602:      */
603:     public function hasPermission($perm)
604:     {
605:         return $this->_perms & $perm;
606:     }
607: 
608:     /**
609:      * Converts an acl string to a Horde Permissions bitmask.
610:      *
611:      * @param string $acl  A standard, IMAP style acl string.
612:      *
613:      * @return integer  Horde Permissions bitmask.
614:      */
615:     protected function _aclToHordePerms($acl)
616:     {
617:         $hPerms = 0;
618: 
619:         if (strpos($acl, 'w') !== false) {
620:             $hPerms |= Horde_Perms::EDIT;
621:         }
622:         if (strpos($acl, 'r') !== false) {
623:             $hPerms |= Horde_Perms::READ;
624:         }
625:         if (strpos($acl, 'd') !== false) {
626:             $hPerms |= Horde_Perms::DELETE;
627:         }
628:         if (strpos($acl, 'l') !== false) {
629:             $hPerms |= Horde_Perms::SHOW;
630:         }
631: 
632:         return $hPerms;
633:     }
634: 
635:     /**
636:      * Creates a new Horde_Share and creates the address book
637:      * on the IMSP server.
638:      *
639:      * @param array  The params for the share.
640:      *
641:      * @return Horde_Share  The share object.
642:      * @throws Turba_Exception
643:      */
644:     public function createShare($share_id, $params)
645:     {
646:         $params['params']['name'] = $this->params['username'];
647:         if (!isset($params['default']) || $params['default'] !== true) {
648:             $params['params']['name'] .= '.' . $params['name'];
649:         }
650: 
651:         $result = Turba::createShare($share_id, $params);
652:         try {
653:             $imsp_result = Horde_Core_Imsp_Utils::createBook($GLOBALS['cfgSources']['imsp'], $params['params']['name']);
654:         } catch (Horde_Imsp_Exception $e) {
655:             throw new Turba_Exception($e);
656:         }
657: 
658:         return $result;
659:     }
660: 
661:     /**
662:      * Helper function to count the occurances of the ':' * delimiter in group
663:      * member entries.
664:      *
665:      * @param string $in  The group member entry.
666:      *
667:      * @return integer  The number of ':' in $in.
668:      */
669:     protected function _countDelimiters($in)
670:     {
671:         $cnt = $pos = 0;
672:         $i = -1;
673:         while (($pos = strpos($in, ':', $pos + 1)) !== false) {
674:             ++$cnt;
675:         }
676: 
677:         return $cnt;
678:     }
679: 
680:     /**
681:      * Returns the owner for this contact. For an IMSP source, this should be
682:      * the name of the address book.
683:      *
684:      * @return string  TODO
685:      */
686:     protected function _getContactOwner()
687:     {
688:        return $this->params['name'];
689:     }
690: 
691:     /**
692:      * Check if the passed in share is the default share for this source.
693:      *
694:      * @see turba/lib/Turba_Driver#checkDefaultShare($share, $srcconfig)
695:      *
696:      * @return TODO
697:      */
698:     public function checkDefaultShare($share, $srcConfig)
699:     {
700:         $params = @unserialize($share->get('params'));
701:         if (!isset($params['default'])) {
702:             $params['default'] = ($params['name'] == $srcConfig['params']['username']);
703:             $share->set('params', serialize($params));
704:             $share->save();
705:         }
706: 
707:         return $params['default'];
708:     }
709: 
710: }
711: 
API documentation generated by ApiGen