2432 lines
87 KiB
PHP
2432 lines
87 KiB
PHP
<?php
|
|
/**
|
|
* Turba external API interface.
|
|
*
|
|
* This file defines Turba's external API interface. Other applications can
|
|
* interact with Turba through this API.
|
|
*
|
|
* Copyright 2009-2017 Horde LLC (http://www.horde.org/)
|
|
*
|
|
* See the enclosed file LICENSE for license information (ASL). If you did
|
|
* did not receive this file, see http://www.horde.org/licenses/apache.
|
|
*
|
|
* @author Michael Slusarz <slusarz@horde.org>
|
|
* @category Horde
|
|
* @license http://www.horde.org/licenses/apache ASL
|
|
* @package Turba
|
|
*/
|
|
class Turba_Api extends Horde_Registry_Api
|
|
{
|
|
/**
|
|
* Links.
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $_links = array(
|
|
'show' => '%application%/contact.php?source=|source|&key=|key|&uid=|uid|',
|
|
'smartmobile_browse' => '%application%/smartmobile.php#browse'
|
|
);
|
|
|
|
/**
|
|
* The listing of API calls that do not require permissions checking.
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $_noPerms = array(
|
|
'getClientSource', 'getClient', 'getClients', 'searchClients'
|
|
);
|
|
|
|
/**
|
|
* Callback for comment API.
|
|
*
|
|
* @param integer $id Internal data identifier.
|
|
*
|
|
* @return mixed Name of object on success, false on failure.
|
|
*/
|
|
public function commentCallback($id)
|
|
{
|
|
if (!$GLOBALS['conf']['comments']['allow']) {
|
|
return false;
|
|
}
|
|
|
|
@list($source, $key) = explode('.', $id, 2);
|
|
if (isset($GLOBALS['cfgSources'][$source]) && $key) {
|
|
try {
|
|
return $GLOBALS['injector']->getInstance('Turba_Factory_Driver')->create($source)->getObject($key)->getValue('name');
|
|
} catch (Horde_Exception $e) {
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Does this API allow comments?
|
|
*
|
|
* @return boolean True if API allows comments.
|
|
*/
|
|
public function hasComments()
|
|
{
|
|
return !empty($GLOBALS['conf']['comments']['allow']);
|
|
}
|
|
|
|
/**
|
|
* Returns a list of available sources.
|
|
*
|
|
* @param boolean $writeable If true, limits to writeable sources.
|
|
* @param boolean $sync_only Only include synchable address books.
|
|
*
|
|
* @return array An array of the available sources. Keys are source IDs,
|
|
* values are source titles.
|
|
*/
|
|
public function sources($writeable = false, $sync_only = false)
|
|
{
|
|
$out = array();
|
|
|
|
foreach (Turba::getAddressBooks($writeable ? Horde_Perms::EDIT : Horde_Perms::READ) as $key => $val) {
|
|
$out[$key] = $val['title'];
|
|
}
|
|
|
|
if ($sync_only) {
|
|
$syncable = unserialize($GLOBALS['prefs']->getValue('sync_books'));
|
|
$out = array_intersect_key($out, array_flip($syncable));
|
|
}
|
|
|
|
return $out;
|
|
}
|
|
|
|
/**
|
|
* Returns a list of fields avaiable in a source.
|
|
*
|
|
* @param string $source The source name.
|
|
*
|
|
* @return array An array describing the fields. Keys are the field name,
|
|
* values are arrays with these keys:
|
|
* - name: (string) Field name.
|
|
* - label: (string) Field label.
|
|
* - search: (boolean) Can this field be searched?
|
|
* - type: (string) See turba/config/attributes.php.
|
|
*
|
|
* @throws Turba_Exception
|
|
*/
|
|
public function fields($source = null)
|
|
{
|
|
global $attributes, $cfgSources;
|
|
|
|
if (is_null($source) || !isset($cfgSources[$source])) {
|
|
throw new Turba_Exception(_("Invalid address book."));
|
|
}
|
|
|
|
$fields = array();
|
|
|
|
foreach (array_keys($cfgSources[$source]['map']) as $name) {
|
|
if (substr($name, 0, 2) != '__') {
|
|
$fields[$name] = array(
|
|
'label' => $attributes[$name]['label'],
|
|
'name' => $name,
|
|
'search' => in_array($name, $cfgSources[$source]['search']),
|
|
'type' => $attributes[$name]['type']
|
|
);
|
|
}
|
|
}
|
|
|
|
return $fields;
|
|
}
|
|
|
|
/**
|
|
* Retrieve the UID for the current user's default Turba share.
|
|
*
|
|
* @return string UID.
|
|
*/
|
|
public function getDefaultShare()
|
|
{
|
|
global $injector, $prefs, $session;
|
|
|
|
// Bring in a clean copy of sources.
|
|
$cfgSources = Turba::availableSources();
|
|
|
|
if ($session->get('turba', 'has_share')) {
|
|
$driver = $injector->getInstance('Turba_Factory_Driver');
|
|
|
|
foreach (Turba::listShares(true) as $uid => $share) {
|
|
$params = @unserialize($share->get('params'));
|
|
if (!empty($params['source'])) {
|
|
try {
|
|
if ($driver->create($uid)->checkDefaultShare($share, $cfgSources[$params['source']])) {
|
|
return $uid;
|
|
}
|
|
} catch (Turba_Exception $e) {}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Return Turba's default_dir as default.
|
|
return $prefs->getValue('default_dir');
|
|
}
|
|
|
|
/**
|
|
* Retrieve the UID for the Global Address List source.
|
|
*
|
|
* @return string|boolean The UID or false if none configured.
|
|
*/
|
|
public function getGalUid()
|
|
{
|
|
return empty($GLOBALS['conf']['gal']['addressbook'])
|
|
? false
|
|
: $GLOBALS['conf']['gal']['addressbook'];
|
|
}
|
|
|
|
/**
|
|
* Browses through Turba's object tree.
|
|
*
|
|
* @param string $path The path of the tree to browse.
|
|
* @param array $properties The item properties to return. Defaults to
|
|
* 'name', 'icon', and 'browseable'.
|
|
*
|
|
* @return array Content of the specified path.
|
|
* @throws Turba_Exception
|
|
* @throws Horde_Exception_NotFound
|
|
*/
|
|
public function browse($path = '',
|
|
$properties = array('name', 'icon', 'browseable'))
|
|
{
|
|
global $injector, $registry, $session;
|
|
|
|
// Strip off the application name if present
|
|
if (substr($path, 0, 5) == 'turba') {
|
|
$path = substr($path, 5);
|
|
}
|
|
$path = trim($path, '/');
|
|
|
|
$results = array();
|
|
|
|
if (empty($path)) {
|
|
/* We always provide the "global" folder which contains address
|
|
* book sources that are shared among all users. Per-user shares
|
|
* are shown in a folder for each respective user. */
|
|
$owners = array(
|
|
'global' => _("Global Address Books")
|
|
);
|
|
|
|
foreach (Turba::listShares() as $share) {
|
|
$owners[$share->get('owner') ? $registry->convertUsername($share->get('owner'), false) : '-system-'] = $share->get('owner') ?: '-system-';
|
|
}
|
|
|
|
$now = time();
|
|
|
|
foreach ($owners as $externalOwner => $internalOwner) {
|
|
if (in_array('name', $properties)) {
|
|
$results['turba/' . $externalOwner]['name'] = $injector
|
|
->getInstance('Horde_Core_Factory_Identity')
|
|
->create($internalOwner)
|
|
->getName();
|
|
}
|
|
if (in_array('icon', $properties)) {
|
|
$results['turba/' . $externalOwner]['icon'] = Horde_Themes::img('turba.png');
|
|
}
|
|
if (in_array('browseable', $properties)) {
|
|
$results['turba/' . $externalOwner]['browseable'] = true;
|
|
}
|
|
if (in_array('read-only', $properties)) {
|
|
$results['turba/' . $externalOwner]['read-only'] = true;
|
|
}
|
|
}
|
|
|
|
return $results;
|
|
}
|
|
|
|
$parts = explode('/', $path);
|
|
|
|
if (count($parts) == 1) {
|
|
/* We should either have the username that is a valid share owner
|
|
* or 'global'. */
|
|
if (empty($parts[0])) {
|
|
// We need either 'global' or a valid username with shares.
|
|
return array();
|
|
}
|
|
|
|
if ($parts[0] == 'global') {
|
|
// The client is requesting a list of global address books.
|
|
$addressbooks = Turba::getAddressBooks();
|
|
foreach ($addressbooks as $addressbook => $info) {
|
|
if ($info['type'] == 'share') {
|
|
// Ignore address book shares in the 'global' folder
|
|
unset($addressbooks[$addressbook]);
|
|
}
|
|
}
|
|
} else {
|
|
/* Assume $parts[0] is a valid username and we need to list
|
|
* their shared addressbooks. */
|
|
if (!$session->get('turba', 'has_share')) {
|
|
// No backends are configured to provide shares
|
|
return array();
|
|
}
|
|
$addressbooks = $injector->getInstance('Turba_Shares')
|
|
->listShares(
|
|
$registry->getAuth(),
|
|
array(
|
|
'attributes' => $registry->convertUsername($parts[0], true),
|
|
'perm' => Horde_Perms::READ
|
|
)
|
|
);
|
|
}
|
|
|
|
$curpath = 'turba/' . $registry->convertUsername($parts[0], true) . '/';
|
|
|
|
foreach ($addressbooks as $addressbook => $info) {
|
|
$label = ($info instanceof Horde_Share_Object)
|
|
? $info->get('name')
|
|
: $info['title'];
|
|
if (in_array('name', $properties)) {
|
|
$results[$curpath . $addressbook]['name'] =
|
|
sprintf(_("Contacts from %s"), $label);
|
|
}
|
|
if (in_array('displayname', $properties)) {
|
|
$results[$curpath . $addressbook]['displayname'] = $label;
|
|
}
|
|
if (in_array('owner', $properties)) {
|
|
$results[$curpath . $addressbook]['owner'] = ($info instanceof Horde_Share_Object)
|
|
? $registry->convertUsername($info->get('owner'), false)
|
|
: '-system-';
|
|
}
|
|
if (in_array('icon', $properties)) {
|
|
$results[$curpath . $addressbook]['icon'] = Horde_Themes::img('turba.png');
|
|
}
|
|
if (in_array('browseable', $properties)) {
|
|
$results[$curpath . $addressbook]['browseable'] = true;
|
|
}
|
|
if (in_array('read-only', $properties) &&
|
|
($info instanceof Horde_Share_Object)) {
|
|
$results[$curpath . $addressbook]['read-only'] = !$info->hasPermission($registry->getAuth(), Horde_Perms::EDIT);
|
|
}
|
|
}
|
|
|
|
return $results;
|
|
}
|
|
|
|
if (count($parts) == 2) {
|
|
/* The client is requesting all contacts from a given
|
|
* addressbook. */
|
|
if (empty($parts[0]) || empty($parts[1])) {
|
|
/* $parts[0] must be either 'global' or a valid user with
|
|
* shares; $parts[1] must be an addressbook ID. */
|
|
return array();
|
|
}
|
|
|
|
$addressbooks = Turba::getAddressBooks();
|
|
if (!isset($addressbooks[$parts[1]])) {
|
|
// We must have a valid addressbook to continue.
|
|
return array();
|
|
}
|
|
|
|
$addressbook = $injector->getInstance('Turba_Factory_Driver')
|
|
->create($parts[1]);
|
|
$owner = $registry->convertUsername($addressbook->getContactOwner(), false);
|
|
$contacts = $addressbook->search(array());
|
|
$contacts->reset();
|
|
|
|
$curpath = 'turba/' . $registry->convertUsername($parts[0], false) . '/' . $parts[1] . '/';
|
|
|
|
while ($contact = $contacts->next()) {
|
|
$key = $curpath . $contact->getValue('__key');
|
|
if (in_array('name', $properties)) {
|
|
$results[$key]['name'] = Turba::formatName($contact);
|
|
}
|
|
if (in_array('owner', $properties)) {
|
|
$results[$key]['owner'] = $owner;
|
|
}
|
|
if (in_array('icon', $properties)) {
|
|
$results[$key]['icon'] = Horde_Themes::img('mime/vcard.png');
|
|
}
|
|
if (in_array('browseable', $properties)) {
|
|
$results[$key]['browseable'] = false;
|
|
}
|
|
if (in_array('read-only', $properties)) {
|
|
$results[$key]['read-only'] = !$addressbook->hasPermission(Horde_Perms::EDIT);
|
|
}
|
|
if (in_array('contenttype', $properties)) {
|
|
$results[$key]['contenttype'] = 'text/x-vcard';
|
|
}
|
|
if (in_array('modified', $properties)) {
|
|
$results[$key]['modified'] = $contact->lastModification() ?: null;
|
|
}
|
|
if (in_array('created', $properties)) {
|
|
$results[$key]['created'] = $this->getActionTimestamp($contact->getValue('__uid'), 'add', $parts[1]);
|
|
}
|
|
}
|
|
|
|
return $results;
|
|
}
|
|
|
|
if (count($parts) == 3) {
|
|
/* The client is requesting an individual contact. */
|
|
$addressbooks = Turba::getAddressBooks();
|
|
if (!isset($addressbooks[$parts[1]])) {
|
|
// We must have a valid addressbook to continue.
|
|
return array();
|
|
}
|
|
|
|
// Load the Turba driver.
|
|
$driver = $injector->getInstance('Turba_Factory_Driver')->create($parts[1]);
|
|
|
|
$contact = $driver->getObject($parts[2]);
|
|
|
|
$result = array(
|
|
'data' => $driver->tovCard($contact, '2.1', null, true)->exportVcalendar(),
|
|
'mimetype' => 'text/x-vcard'
|
|
);
|
|
$modified = $this->_modified($contact->getValue('__uid'), $parts[1]);
|
|
if (!empty($modified)) {
|
|
$result['mtime'] = $modified;
|
|
}
|
|
return $result;
|
|
}
|
|
|
|
throw new Turba_Exception(_("Malformed request."));
|
|
}
|
|
|
|
/**
|
|
* Deletes a file from the Turba tree.
|
|
*
|
|
* @param string $path The path to the file.
|
|
*
|
|
* @return string The event's UID.
|
|
* @throws Turba_Exception
|
|
*/
|
|
public function path_delete($path)
|
|
{
|
|
// Strip off the application name if present
|
|
if (substr($path, 0, 5) == 'turba') {
|
|
$path = substr($path, 5);
|
|
}
|
|
|
|
$parts = explode('/', trim($path, '/'));
|
|
|
|
if (count($parts) < 3) {
|
|
// Deletes must be on individual contacts.
|
|
throw new Turba_Exception(_("Delete denied."));
|
|
}
|
|
|
|
if (!array_key_exists($parts[1], Turba::getAddressBooks())) {
|
|
throw new Turba_Exception(_("Address book does not exist"));
|
|
}
|
|
|
|
return $GLOBALS['injector']->getInstance('Turba_Factory_Driver')->create($parts[1])->delete($parts[2]);
|
|
}
|
|
|
|
/**
|
|
* Returns an array of UIDs for all contacts that the current user is
|
|
* authorized to see.
|
|
*
|
|
* @param string|array $sources The name(s) of the source(s) to return
|
|
* contacts of. If empty, the current user's
|
|
* sync sources or default source are used.
|
|
*
|
|
* @return array An array of UIDs for all contacts the user can access.
|
|
* @throws Turba_Exception
|
|
*/
|
|
public function listUids($sources = null)
|
|
{
|
|
$driver = $GLOBALS['injector']->getInstance('Turba_Factory_Driver');
|
|
$uids = array();
|
|
|
|
foreach ($this->_getSources($sources) as $source) {
|
|
try {
|
|
$results = $driver->create($source)->search(array());
|
|
} catch (Turba_Exception $e) {
|
|
throw new Turba_Exception(sprintf(_("Error searching the address book: %s"), $e->getMessage()));
|
|
}
|
|
|
|
foreach ($results->objects as $o) {
|
|
if (!$o->isGroup()) {
|
|
$uids[] = $o->getValue('__uid');
|
|
}
|
|
}
|
|
}
|
|
|
|
return $uids;
|
|
}
|
|
|
|
/**
|
|
* Returns an array of UIDs for contacts that have had a given action
|
|
* since a certain time.
|
|
*
|
|
* @param string $action The action to check for - add, modify, or
|
|
* delete.
|
|
* @param integer $timestamp The time to start the search.
|
|
* @param string|array $sources The source(s) for which to retrieve the
|
|
* history.
|
|
* @param integer $end The optional ending timestamp.
|
|
* @param boolean $isModSeq If true, $timestamp and $end are
|
|
* modification sequences and not timestamps.
|
|
* @since 4.1.1
|
|
*
|
|
* @return array An array of UIDs matching the action and time criteria.
|
|
*
|
|
* @throws Turba_Exception
|
|
*/
|
|
public function listBy($action, $timestamp, $sources = null, $end = null, $isModSeq = false)
|
|
{
|
|
$driver = $GLOBALS['injector']->getInstance('Turba_Factory_Driver');
|
|
$history = $GLOBALS['injector']->getInstance('Horde_History');
|
|
$filter = array(
|
|
array(
|
|
'field' => 'action',
|
|
'op' => '=',
|
|
'value' => $action
|
|
)
|
|
);
|
|
$uids = array();
|
|
|
|
if (!empty($end) && !$isModSeq) {
|
|
$filter[] = array(
|
|
'field' => 'ts',
|
|
'op' => '<',
|
|
'value' => $end
|
|
);
|
|
}
|
|
|
|
foreach ($this->_getSources($sources) as $source) {
|
|
$sdriver = $driver->create($source);
|
|
if (!$isModSeq) {
|
|
$histories = $history->getByTimestamp(
|
|
'>', $timestamp, $filter,
|
|
'turba:' . $sdriver->getName()
|
|
);
|
|
} else {
|
|
$histories = $history->getByModSeq(
|
|
$timestamp, $end, $filter, 'turba:' . $sdriver->getName());
|
|
}
|
|
|
|
// Filter out groups
|
|
$nguids = str_replace(
|
|
'turba:' . $sdriver->getName() . ':',
|
|
'',
|
|
array_keys($histories)
|
|
);
|
|
|
|
$include = array();
|
|
foreach ($nguids as $uid) {
|
|
if ($action != 'delete') {
|
|
$list = $sdriver->search(array('__uid' => $uid));
|
|
if ($list->count()) {
|
|
$object = $list->next();
|
|
if ($object->isGroup()) {
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
$include[] = $uid;
|
|
}
|
|
|
|
// Strip leading turba:addressbook:.
|
|
$uids = array_merge($uids, $include);
|
|
}
|
|
|
|
return $uids;
|
|
}
|
|
|
|
/**
|
|
* Method for obtaining all server changes between two timestamps.
|
|
* Essentially a wrapper around listBy(), but returns an array containing
|
|
* all adds, edits, and deletions.
|
|
*
|
|
* @param integer $start The starting timestamp
|
|
* @param integer $end The ending timestamp.
|
|
* @param boolean $isModSeq If true, $start and $end are modification
|
|
* sequences and not timestamps. @since 4.1.1
|
|
* @param string|array $sources The sources to check. @since 4.2.0
|
|
*
|
|
* @return array A hash with 'add', 'modify' and 'delete' arrays.
|
|
*/
|
|
public function getChanges($start, $end, $isModSeq = false, $sources = null)
|
|
{
|
|
return array(
|
|
'add' => $this->listBy('add', $start, $sources, $end, $isModSeq),
|
|
'modify' => $this->listBy('modify', $start, $sources, $end, $isModSeq),
|
|
'delete' => $this->listBy('delete', $start, $sources, $end, $isModSeq)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Return all changes occuring between the specified modification
|
|
* sequences.
|
|
*
|
|
* @param integer $start The starting modseq.
|
|
* @param integer $end The ending modseq.
|
|
* @param string|array $sources The sources to check. @since 4.2.0
|
|
*
|
|
* @return array The changes @see getChanges()
|
|
* @since 4.1.1
|
|
*/
|
|
public function getChangesByModSeq($start, $end, $sources = null)
|
|
{
|
|
return $this->getChanges($start, $end, true, $sources);
|
|
}
|
|
|
|
/**
|
|
* Returns the timestamp of an operation for a given UID and action.
|
|
*
|
|
* @param string $uid The UID to look for.
|
|
* @param string $action The action to check for - add, modify, or
|
|
* delete.
|
|
* @param string|array $sources The source(s) for which to retrieve the
|
|
* history.
|
|
* @param boolean $modSeq Request a modification sequence instead of
|
|
* timestamp. @since 4.1.1
|
|
*
|
|
* @return integer The timestamp for this action.
|
|
*
|
|
* @throws Turba_Exception
|
|
*/
|
|
public function getActionTimestamp($uid, $action, $sources = null, $modSeq = false)
|
|
{
|
|
$driver = $GLOBALS['injector']->getInstance('Turba_Factory_Driver');
|
|
$history = $GLOBALS['injector']->getInstance('Horde_History');
|
|
$last = 0;
|
|
|
|
foreach ($this->_getSources($sources) as $source) {
|
|
if (!$modSeq) {
|
|
$ts = $history->getActionTimestamp(
|
|
'turba:' . $driver->create($source)->getName() . ':' . $uid,
|
|
$action);
|
|
} else {
|
|
$ts = $history->getActionModSeq(
|
|
'turba:' . $driver->create($source)->getName() . ':' . $uid,
|
|
$action);
|
|
}
|
|
if (!empty($ts) && $ts > $last) {
|
|
$last = $ts;
|
|
}
|
|
}
|
|
|
|
return $last;
|
|
}
|
|
|
|
/**
|
|
* Return the largest modification sequence from the history backend.
|
|
*
|
|
* @param string $id Addressbook id to return highest MODSEQ for. If
|
|
* null, the highest MODSEQ across all addressbooks is
|
|
* returned. @since 4.2.0
|
|
*
|
|
* @return integer The modseq.
|
|
* @since 4.1.1
|
|
*/
|
|
public function getHighestModSeq($id = null)
|
|
{
|
|
$parent = 'turba';
|
|
if (!empty($id)) {
|
|
$parent .= ':' . $id;
|
|
}
|
|
return $GLOBALS['injector']->getInstance('Horde_History')->getHighestModSeq($parent);
|
|
}
|
|
|
|
/**
|
|
* Import a contact represented in the specified contentType.
|
|
*
|
|
* @param string $content The content of the contact.
|
|
* @param string $contentType What format is the data in? Currently
|
|
* supports array, text/directory, text/vcard,
|
|
* text/x-vcard, and activesync.
|
|
* @param string $source The source into which the contact will be
|
|
* imported.
|
|
* @param array $options Additional options:
|
|
* - match_on_email: (boolean) If true, will detect entry as duplicate
|
|
* if ANY email field matches. Useful for
|
|
* automatically adding contacts from an
|
|
* email application, such as IMP.
|
|
* @since 4.2.9
|
|
*
|
|
* @return string The new UID.
|
|
*
|
|
* @throws Turba_Exception
|
|
* @throws Turba_Exception_ObjectExists
|
|
*/
|
|
public function import($content, $contentType = 'array', $source = null, array $options = array())
|
|
{
|
|
global $cfgSources, $injector, $prefs;
|
|
|
|
/* Get default address book from user preferences. */
|
|
if (empty($source) &&
|
|
!($source = $prefs->getValue('default_dir'))) {
|
|
// On new installations default_dir is not set. Try default
|
|
// addressbook if it's editable. Otherwise use first editable
|
|
// addressbook.
|
|
$edit_sources = Turba::getAddressBooks(Horde_Perms::EDIT);
|
|
$default_source = Turba::getDefaultAddressbook();
|
|
if (isset($edit_sources[$default_source])) {
|
|
// use default addressbook
|
|
$source = $default_source;
|
|
} else {
|
|
// Use first writable source
|
|
$source = reset($edit_sources);
|
|
}
|
|
}
|
|
|
|
// Check existence of and permissions on the specified source.
|
|
if (!isset($cfgSources[$source])) {
|
|
throw new Turba_Exception(sprintf(_("Invalid address book: %s"), $source));
|
|
}
|
|
|
|
$driver = $injector
|
|
->getInstance('Turba_Factory_Driver')
|
|
->create($source);
|
|
|
|
if (!$driver->hasPermission(Horde_Perms::EDIT)) {
|
|
throw new Turba_Exception(_("Permission denied"));
|
|
}
|
|
|
|
if (!($content instanceof Horde_Icalendar_Vcard)) {
|
|
switch ($contentType) {
|
|
case 'activesync':
|
|
$content = $driver->fromASContact($content);
|
|
break;
|
|
|
|
case 'array':
|
|
if (!isset($content['emails']) && isset($content['email'])) {
|
|
$content['emails'] = $content['email'];
|
|
}
|
|
break;
|
|
|
|
case 'text/x-vcard':
|
|
case 'text/vcard':
|
|
case 'text/directory':
|
|
$iCal = new Horde_Icalendar();
|
|
if (!$iCal->parsevCalendar($content)) {
|
|
throw new Turba_Exception(_("There was an error importing the iCalendar data."));
|
|
}
|
|
|
|
switch ($iCal->getComponentCount()) {
|
|
case 0:
|
|
throw new Turba_Exception(_("No vCard data was found."));
|
|
|
|
case 1:
|
|
$content = $driver->toHash($iCal->getComponent(0));
|
|
break;
|
|
|
|
default:
|
|
$ids = array();
|
|
|
|
foreach ($iCal->getComponents() as $c) {
|
|
if ($c instanceof Horde_Icalendar_Vcard) {
|
|
$content = $driver->toHash($c);
|
|
$result = $driver->search($content);
|
|
if (count($result)) {
|
|
continue;
|
|
}
|
|
|
|
$ids[] = $driver->add($content);
|
|
}
|
|
}
|
|
|
|
return $ids;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
throw new Turba_Exception(sprintf(_("Unsupported Content-Type: %s"), $contentType));
|
|
}
|
|
}
|
|
|
|
if ($content instanceof Horde_Icalendar_Vcard) {
|
|
$content = $driver->toHash($content);
|
|
}
|
|
|
|
if (!empty($options['match_on_email'])) {
|
|
$content_copy = array();
|
|
foreach (Turba::getAvailableEmailFields() as $field) {
|
|
if (!empty($content[$field])) {
|
|
$rfc = new Horde_Mail_Rfc822();
|
|
$email = $rfc->parseAddressList($content[$field]);
|
|
$content_copy[$field] = (string)$email;
|
|
}
|
|
}
|
|
} else {
|
|
$content_copy = $content;
|
|
}
|
|
|
|
// Check if the entry already exists in the data source.
|
|
$result = $driver->search(
|
|
$content_copy, null, !empty($options['match_on_email']) ? 'OR' : 'AND');
|
|
|
|
if (count($result)) {
|
|
throw new Turba_Exception_ObjectExists(_("Already Exists"));
|
|
}
|
|
|
|
// We can't use $object->setValue() here since that cannot be used
|
|
// with composite fields.
|
|
$hooks = $injector->getInstance('Horde_Core_Hooks');
|
|
if ($hooks->hookExists('encode_attribute', 'turba')) {
|
|
foreach ($content as $attribute => &$value) {
|
|
try {
|
|
$value = $hooks->callHook(
|
|
'encode_attribute',
|
|
'turba',
|
|
array($attribute, $value, null, null)
|
|
);
|
|
} catch (Turba_Exception $e) {}
|
|
}
|
|
}
|
|
$result = $driver->add($content);
|
|
|
|
return $driver->getObject($result)->getValue('__uid');
|
|
}
|
|
|
|
/**
|
|
* Export a contact, identified by UID, in the requested contentType.
|
|
*
|
|
* @param string $uid Identify the contact to export.
|
|
* @param mixed $contentType What format should the data be in?
|
|
* - text/directory: Returns RFC2426 vcard3.0
|
|
* - text/vcard: Returns RFC2426 vcard3.0
|
|
* - text/x-vcard: Returns imc.org vcard 2.1 format.
|
|
* - array: Returns a raw array
|
|
* - activesync: Returns a Horde_ActiveSync_Message_Contact:: object
|
|
* @param string|array $sources The source(s) from which the contact will
|
|
* be exported.
|
|
* @param array $fields Hash of field names and
|
|
* Horde_SyncMl_Property properties with the
|
|
* requested fields.
|
|
* @param array $options Any additional options to be passed to the
|
|
* exporter. Currently supported:
|
|
* - skip_empty: (boolean) {text/vcard or text/x-vcard} Set to
|
|
* true to not output empty properties.
|
|
* DEFAULT: false.
|
|
* - protocolversion: (float) {activesync} The EAS version to support
|
|
* DEFAULT: 2.5
|
|
* - bodyprefs: (array) {activesync} A BODYPREFERENCE array.
|
|
* DEFAULT: none (No body prefs enforced).
|
|
* - truncation: (integer) {activesync} Truncate event body to this
|
|
* length.
|
|
* DEFAULT: none (No truncation).
|
|
*
|
|
*
|
|
* @return mixed The requested data.
|
|
* @throws Turba_Exception
|
|
*/
|
|
public function export($uid, $contentType, $sources = null, $fields = null, array $options = array())
|
|
{
|
|
if (empty($uid)) {
|
|
throw new Turba_Exception(_("Invalid ID"));
|
|
}
|
|
|
|
$driver = $GLOBALS['injector']->getInstance('Turba_Factory_Driver');
|
|
|
|
foreach ($this->_getSources($sources) as $source) {
|
|
$sdriver = $driver->create($source);
|
|
|
|
if (!$sdriver->hasPermission(Horde_Perms::READ)) {
|
|
continue;
|
|
}
|
|
|
|
$result = $sdriver->search(array('__uid' => $uid));
|
|
if (count($result) == 0) {
|
|
continue;
|
|
} elseif (count($result) > 1) {
|
|
throw new Turba_Exception(sprintf("Internal Horde Error: multiple Turba objects with same objectId %s.", $uid));
|
|
}
|
|
|
|
$version = '3.0';
|
|
list($contentType,) = explode(';', $contentType);
|
|
|
|
switch ($contentType) {
|
|
case 'text/x-vcard':
|
|
$version = '2.1';
|
|
// Fall-through
|
|
|
|
case 'text/vcard':
|
|
case 'text/directory':
|
|
$export = '';
|
|
foreach ($result->objects as $obj) {
|
|
$vcard = $sdriver->tovCard($obj, $version, $fields, !empty($options['skip_empty']));
|
|
/* vCards are not enclosed in
|
|
* BEGIN:VCALENDAR..END:VCALENDAR. Export the individual
|
|
* cards instead. */
|
|
$export .= $vcard->exportvCalendar();
|
|
}
|
|
return $export;
|
|
|
|
case 'array':
|
|
$attributes = array();
|
|
foreach ($result->objects as $object) {
|
|
foreach (array_keys($GLOBALS['cfgSources'][$source]['map']) as $field) {
|
|
$attributes[$field] = $object->getValue($field);
|
|
}
|
|
}
|
|
return $attributes;
|
|
|
|
case 'activesync':
|
|
foreach ($result->objects as $object) {
|
|
$return = $object;
|
|
}
|
|
return $sdriver->toASContact($return, $options);
|
|
|
|
default:
|
|
throw new Turba_Exception(sprintf(_("Unsupported Content-Type: %s"), $contentType));
|
|
}
|
|
}
|
|
|
|
throw new Turba_Exception(sprintf(_("Object %s not found."), $uid));
|
|
}
|
|
|
|
/**
|
|
* Exports the user's own contact as a vCard string.
|
|
*
|
|
* @return string The requested vCard data.
|
|
* @throws Turba_Exception
|
|
*/
|
|
public function ownVCard()
|
|
{
|
|
$contact = $this->getOwnContactObject();
|
|
|
|
$vcard = $GLOBALS['injector']->getInstance('Turba_Factory_Driver')->create($contact['source'])->tovCard($contact['contact'], '3.0', null, true);
|
|
$vcard->setAttribute('VERSION', '3.0');
|
|
|
|
return $vcard->exportvCalendar();
|
|
}
|
|
|
|
/**
|
|
* Export the user's own contact as a hash.
|
|
*
|
|
* @return array The contact hash.
|
|
* @throws Turba_Exception
|
|
*/
|
|
public function ownContact()
|
|
{
|
|
$contact = $this->getOwnContactObject();
|
|
return $contact['contact']->getAttributes();
|
|
}
|
|
|
|
/**
|
|
* Helper function to return the user's own contact object
|
|
*
|
|
* @return array An array containing the following keys:
|
|
* - contact: (Turba_Object) Object representing the user's own contact.
|
|
* - source: (string) The source of the user's own contact.
|
|
* @throws Turba_Exception
|
|
*/
|
|
public function getOwnContactObject()
|
|
{
|
|
$own_contact = $GLOBALS['prefs']->getValue('own_contact');
|
|
if (empty($own_contact)) {
|
|
throw new Turba_Exception(_("You didn't mark a contact as your own yet."));
|
|
}
|
|
@list($source, $id) = explode(';', $own_contact);
|
|
|
|
if (!isset($GLOBALS['cfgSources'][$source])) {
|
|
throw new Turba_Exception(_("The address book with your own contact doesn't exist anymore."));
|
|
}
|
|
|
|
$driver = $GLOBALS['injector']->getInstance('Turba_Factory_Driver')->create($source);
|
|
|
|
if (!$driver->hasPermission(Horde_Perms::READ)) {
|
|
throw new Turba_Exception(_("You don't have sufficient permissions to read the address book that contains your own contact."));
|
|
}
|
|
|
|
try {
|
|
$contact = $driver->getObject($id);
|
|
} catch (Horde_Exception_NotFound $e) {
|
|
throw new Turba_Exception(_("Your own contact cannot be found in the address book."));
|
|
}
|
|
|
|
return array(
|
|
'contact' => $contact,
|
|
'source'=> $source
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Deletes a contact identified by UID.
|
|
*
|
|
* @param string|array $uid Identify the contact to delete, either a
|
|
* single UID or an array.
|
|
* @param string|array $sources The source(s) from which the contact will
|
|
* be deleted.
|
|
*
|
|
* @return boolean Success or failure.
|
|
* @throws Turba_Exception
|
|
*/
|
|
public function delete($uid, $sources = null)
|
|
{
|
|
if (empty($uid)) {
|
|
throw new Turba_Exception(_("Invalid ID"));
|
|
}
|
|
|
|
// Handle an array of UIDs for convenience of deleting multiple
|
|
// contacts at once.
|
|
if (is_array($uid)) {
|
|
foreach ($uid as $g) {
|
|
if (!$this->delete($g, $sources)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
$driver = $GLOBALS['injector']->getInstance('Turba_Factory_Driver');
|
|
|
|
foreach ($this->_getSources($sources) as $source) {
|
|
$sdriver = $driver->create($source);
|
|
|
|
if (!$GLOBALS['registry']->isAdmin() &&
|
|
!$sdriver->hasPermission(Horde_Perms::DELETE)) {
|
|
continue;
|
|
}
|
|
|
|
// If the objectId isn't in $source in the first place, just
|
|
// return true. Otherwise, try to delete it and return success or
|
|
// failure.
|
|
$result = $sdriver->search(array('__uid' => $uid));
|
|
if (count($result) != 0) {
|
|
$r = $result->objects[0];
|
|
try {
|
|
$sdriver->delete($r->getValue('__key'));
|
|
} catch (Turba_Exception $e) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Replaces the contact identified by UID with the content represented in
|
|
* the specified contentType.
|
|
*
|
|
* @param string $uid Idenfity the contact to replace.
|
|
* @param mixed $content The content of the contact.
|
|
* @param string $contentType What format is the data in? Currently
|
|
* supports array, text/directory,
|
|
* text/vcard, text/x-vcard and activesync.
|
|
* @param string|array $sources The source(s) where the contact will be
|
|
* replaced.
|
|
*
|
|
* @return boolean Success or failure.
|
|
* @throws Turba_Exception
|
|
*/
|
|
public function replace($uid, $content, $contentType, $sources = null)
|
|
{
|
|
if (empty($uid)) {
|
|
throw new Turba_Exception(_("Invalid contact unique ID"));
|
|
}
|
|
|
|
$driver = $GLOBALS['injector']->getInstance('Turba_Factory_Driver');
|
|
|
|
foreach ($this->_getSources($sources) as $source) {
|
|
$sdriver = $driver->create($source);
|
|
|
|
// Check permissions.
|
|
if (!$sdriver->hasPermission(Horde_Perms::EDIT)) {
|
|
continue;
|
|
}
|
|
|
|
$result = $sdriver->search(array('__uid' => $uid));
|
|
if (!count($result)) {
|
|
continue;
|
|
} elseif (count($result) > 1) {
|
|
throw new Turba_Exception(sprintf(_("Multiple contacts found with same unique ID %s."), $uid));
|
|
}
|
|
|
|
$object = $result->objects[0];
|
|
|
|
switch ($contentType) {
|
|
case 'activesync':
|
|
$content = $sdriver->fromASContact($content);
|
|
foreach ($content as $attribute => $value) {
|
|
if ($attribute != '__key') {
|
|
$object->setValue($attribute, $value);
|
|
}
|
|
}
|
|
return $object->store();
|
|
|
|
case 'array':
|
|
break;
|
|
|
|
case 'text/x-vcard':
|
|
case 'text/vcard':
|
|
case 'text/directory':
|
|
$iCal = new Horde_Icalendar();
|
|
if (!$iCal->parsevCalendar($content)) {
|
|
throw new Turba_Exception(_("There was an error importing the iCalendar data."));
|
|
}
|
|
|
|
switch ($iCal->getComponentCount()) {
|
|
case 0:
|
|
throw new Turba_Exception(_("No vCard data was found."));
|
|
|
|
case 1:
|
|
$content = $sdriver->toHash($iCal->getComponent(0));
|
|
break;
|
|
|
|
default:
|
|
throw new Turba_Exception(_("Only one vcard supported."));
|
|
}
|
|
break;
|
|
|
|
default:
|
|
throw new Turba_Exception(sprintf(_("Unsupported Content-Type: %s"), $contentType));
|
|
}
|
|
|
|
foreach ($content as $attribute => $value) {
|
|
if ($attribute != '__key') {
|
|
$object->setValue($attribute, $value);
|
|
}
|
|
}
|
|
|
|
return $object->store();
|
|
}
|
|
|
|
throw new Turba_Exception(sprintf(_("Object %s not found."), $uid));
|
|
}
|
|
|
|
/**
|
|
* Returns a contact search result.
|
|
*
|
|
* @param mixed $names The search filter values.
|
|
* @param array $opts Optional parameters:
|
|
* - customStrict: (array) An array of fields that must match exactly.
|
|
* DEFAULT: None
|
|
* - fields: (array) The fields to search on.
|
|
* DEFAULT: All fields
|
|
* - forceSource: (boolean) Whether to use the specified sources, even
|
|
* if they have been disabled in the preferences?
|
|
* DEFAULT: false
|
|
* - matchBegin: (boolean) Match word boundaries only?
|
|
* DEFAULT: false
|
|
* - returnFields: Only return these fields. Note that the __key field
|
|
* will always be returned.
|
|
* DEFAULT: Return all fields.
|
|
* - rfc822Return: Return a Horde_Mail_Rfc822_List object.
|
|
* DEFAULT: Returns an array of search results.
|
|
* - sources: (array) The sources to search in.
|
|
* DEFAULT: Search the user's default address book
|
|
* - count_only: (boolean) If true, only return the count of matching
|
|
* results.
|
|
* DEFAULT: false (Return the full data set).
|
|
* - emailSearch: (boolean) If true, indicates this is an email search
|
|
* like e.g., for an autocompleter. Ensures that ALL email
|
|
* fields are both included in 'fields' and returned in the
|
|
* results, regardless of the requested returnFields value.
|
|
* @todo Make this a separate API method in H6 (or separate
|
|
* API "module", e.g., an 'email search' API).
|
|
* @since 4.3.0
|
|
*
|
|
* @return mixed Either a hash containing the search results or a
|
|
* Rfc822 List object (if 'rfc822Return' is true).
|
|
* @throws Turba_Exception
|
|
*/
|
|
public function search($names = null, array $opts = array())
|
|
{
|
|
global $attributes, $cfgSources, $injector;
|
|
|
|
$opts = array_merge(array(
|
|
'fields' => array(),
|
|
'forceSource' => false,
|
|
'matchBegin' => false,
|
|
'returnFields' => array(),
|
|
'rfc822Return' => false,
|
|
'sources' => array(),
|
|
'customStrict' => array(),
|
|
'count_only' => false,
|
|
), $opts);
|
|
|
|
$results = !empty($opts['count_only'])
|
|
? 0
|
|
: (empty($opts['rfc822Return'])
|
|
? array()
|
|
: new Horde_Mail_Rfc822_List());
|
|
|
|
if (!isset($cfgSources) ||
|
|
!is_array($cfgSources) ||
|
|
!count($cfgSources) ||
|
|
is_null($names)) {
|
|
return $results;
|
|
}
|
|
|
|
if (!is_array($names)) {
|
|
$names = array($names);
|
|
}
|
|
|
|
if (!$opts['forceSource']) {
|
|
// Make sure the selected source is activated in Turba.
|
|
$addressbooks = array_keys(Turba::getAddressBooks());
|
|
foreach (array_keys($opts['sources']) as $id) {
|
|
if (!in_array($opts['sources'][$id], $addressbooks)) {
|
|
unset($opts['sources'][$id]);
|
|
}
|
|
}
|
|
}
|
|
|
|
// ...and ensure the default source is used as a default.
|
|
if (!count($opts['sources'])) {
|
|
$opts['sources'] = array(Turba::getDefaultAddressbook());
|
|
if (!empty($opts['fields']) && empty($opts['fields'][$opts['sources'][0]])) {
|
|
$opts['fields'][$opts['sources'][0]] = $opts['fields'];
|
|
}
|
|
}
|
|
|
|
$driver = $injector->getInstance('Turba_Factory_Driver');
|
|
foreach ($opts['sources'] as $source) {
|
|
// Skip invalid sources -or-
|
|
// skip sources that aren't browseable if the search is empty.
|
|
if (!isset($cfgSources[$source]) ||
|
|
(empty($cfgSources[$source]['browse']) &&
|
|
(!count($names) ||
|
|
((count($names) == 1) && empty($names[0]))))) {
|
|
|
|
continue;
|
|
}
|
|
|
|
// Ensure we have search fields for each source.
|
|
if (empty($opts['fields'][$source])) {
|
|
$opts['fields'][$source] = $cfgSources[$source]['search'];
|
|
}
|
|
|
|
// If this is an email search, ensure we are searching for and
|
|
// returning all email-type fields.
|
|
if (!empty($opts['emailSearch'])) {
|
|
$opts['returnFields'] = array_merge(
|
|
$opts['returnFields'],
|
|
Turba::getAvailableEmailFields($source, false)
|
|
);
|
|
|
|
$opts['fields'][$source] = array_merge(
|
|
$opts['fields'][$source],
|
|
Turba::getAvailableEmailFields($source)
|
|
);
|
|
}
|
|
|
|
$sdriver = $driver->create($source);
|
|
foreach ($names as $name) {
|
|
$trimname = trim($name);
|
|
$out = $criteria = array();
|
|
unset($tname);
|
|
|
|
if (strlen($trimname)) {
|
|
if (isset($opts['fields'][$source])) {
|
|
foreach ($opts['fields'][$source] as $field) {
|
|
$criteria[$field] = $trimname;
|
|
}
|
|
}
|
|
}
|
|
|
|
try {
|
|
$search = $sdriver->search(
|
|
$criteria,
|
|
Turba::getPreferredSortOrder(),
|
|
'OR',
|
|
$opts['returnFields'],
|
|
$opts['customStrict'],
|
|
$opts['matchBegin'],
|
|
$opts['count_only']
|
|
);
|
|
} catch (Exception $e) {
|
|
continue;
|
|
}
|
|
|
|
if ($opts['count_only']) {
|
|
$results += $search;
|
|
|
|
continue;
|
|
} elseif (!($search instanceof Turba_List)) {
|
|
continue;
|
|
}
|
|
|
|
$rfc822 = new Horde_Mail_Rfc822();
|
|
|
|
while ($ob = $search->next()) {
|
|
$emails = $seen = array();
|
|
|
|
if ($ob->isGroup()) {
|
|
/* Is a distribution list. */
|
|
$members = $ob->listMembers();
|
|
if (!($members instanceof Turba_List) ||
|
|
!count($members)) {
|
|
continue;
|
|
}
|
|
|
|
$listatt = $ob->getAttributes();
|
|
$listName = $ob->getValue('name');
|
|
|
|
while ($ob = $members->next()) {
|
|
foreach (array_keys($ob->getAttributes()) as $key) {
|
|
$value = $ob->getValue($key);
|
|
if (empty($value)) {
|
|
continue;
|
|
}
|
|
|
|
$seen_key = trim(Horde_String::lower($ob->getValue('name'))) . trim(Horde_String::lower(is_array($value) ? $value['load']['file'] : $value));
|
|
|
|
if (isset($attributes[$key]) &&
|
|
($attributes[$key]['type'] == 'email') &&
|
|
empty($seen[$seen_key])) {
|
|
$emails[] = $value;
|
|
$seen[$seen_key] = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (empty($opts['rfc822Return'])) {
|
|
$out[] = array(
|
|
'email' => implode(', ', $emails),
|
|
'id' => $listatt['__key'],
|
|
'name' => $listName,
|
|
'source' => $source,
|
|
'__key' => $listatt['__key'],
|
|
'__type' => $listatt['__type']
|
|
);
|
|
} else {
|
|
$results->add(new Horde_Mail_Rfc822_Group($listName, $emails));
|
|
}
|
|
} else {
|
|
/* Not a group. */
|
|
$att = array(
|
|
'__key' => $ob->getValue('__key')
|
|
);
|
|
|
|
foreach (array_keys($ob->driver->getCriteria()) as $key) {
|
|
if (empty($opts['returnFields']) ||
|
|
(!empty($opts['returnFields']) && in_array($key, $opts['returnFields']))) {
|
|
$att[$key] = $ob->getValue($key);
|
|
}
|
|
}
|
|
|
|
$email = new Horde_Mail_Rfc822_List();
|
|
|
|
$display_name = ($ob->hasValue('name') || !isset($ob->driver->alternativeName))
|
|
? Turba::formatName($ob)
|
|
: $ob->getValue($ob->driver->alternativeName);
|
|
unset($tdisplay_name);
|
|
$email_fields = array();
|
|
foreach (array_keys($att) as $key) {
|
|
// Only concerned about keys that we want returned.
|
|
if (!empty($opts['returnFields']) &&
|
|
!in_array($key, $opts['returnFields'])) {
|
|
continue;
|
|
}
|
|
|
|
if ($ob->getValue($key) &&
|
|
isset($attributes[$key]) &&
|
|
($attributes[$key]['type'] == 'email')) {
|
|
$e_val = $ob->getValue($key);
|
|
$email_fields[$key] = $e_val;
|
|
if (strlen($trimname)) {
|
|
/* Ticket #12480: Don't return email if it
|
|
* doesn't contain the search string, since
|
|
* an entry can contain multiple e-mail
|
|
* fields. Return all e-mails if it
|
|
* occurs in the name. */
|
|
if (!isset($tname)) {
|
|
$tname = Horde_String_Transliterate::toAscii($name);
|
|
}
|
|
if (!isset($tdisplay_name)) {
|
|
$tdisplay_name = Horde_String_Transliterate::toAscii($display_name);
|
|
}
|
|
|
|
$add = ((Horde_String::ipos(Horde_String_Transliterate::toAscii($e_val), $tname) !== false) ||
|
|
(Horde_String::ipos($tdisplay_name, $tname) !== false));
|
|
} else {
|
|
$add = true;
|
|
}
|
|
|
|
if ($add) {
|
|
// Multiple addresses support
|
|
$email->add($rfc822->parseAddressList($e_val, array(
|
|
'limit' => (isset($attributes[$key]['params']) && is_array($attributes[$key]['params']) && !empty($attributes[$key]['params']['allow_multi'])) ? 0 : 1
|
|
)));
|
|
}
|
|
}
|
|
}
|
|
|
|
// If we haven't added any results yet, add any available
|
|
// addresses since the search term might not have matched
|
|
// the display_name OR any of the email addresses.
|
|
// See Bug: 13945
|
|
if (!count($email)) {
|
|
foreach ($email_fields as $e_field => $e_value) {
|
|
$email->add($rfc822->parseAddressList($e_value, array(
|
|
'limit' => (isset($attributes[$e_field]['params']) && is_array($attributes[$e_field]['params']) && !empty($attributes[$e_field]['params']['allow_multi'])) ? 0 : 1
|
|
)));
|
|
}
|
|
}
|
|
if (count($email)) {
|
|
foreach ($email as $val) {
|
|
$seen_key = trim(Horde_String::lower($display_name)) . '/' . Horde_String::lower($val->bare_address);
|
|
if (empty($seen[$seen_key])) {
|
|
$seen[$seen_key] = true;
|
|
if (empty($opts['rfc822Return'])) {
|
|
$emails[] = $val->bare_address;
|
|
} else {
|
|
$val->personal = $display_name;
|
|
$results->add($val);
|
|
}
|
|
}
|
|
}
|
|
} elseif (empty($opts['rfc822Return'])) {
|
|
$emails[] = null;
|
|
}
|
|
|
|
if (empty($opts['rfc822Return'])) {
|
|
foreach ($emails as $val) {
|
|
$atts = array(
|
|
'__type' => 'Object',
|
|
'id' => $att['__key'],
|
|
'source' => $source
|
|
);
|
|
if (empty($opts['returnFields'])) {
|
|
$atts = array(
|
|
'__type' => 'Object',
|
|
'id' => $att['__key'],
|
|
'source' => $source,
|
|
'email' => $val,
|
|
'name' => $display_name
|
|
);
|
|
} else {
|
|
$atts = array();
|
|
$fields = array(
|
|
'__type' => 'Object',
|
|
'id' => $att['__key'],
|
|
'source' => $source,
|
|
'email' => $val,
|
|
'name' => $display_name
|
|
);
|
|
foreach($fields as $field => $value) {
|
|
if (in_array($field, $opts['returnFields'])) {
|
|
$atts[$field] = $value;
|
|
}
|
|
}
|
|
}
|
|
|
|
$out[] = array_merge($att, $atts);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!empty($out)) {
|
|
$results[$name] = $out;
|
|
}
|
|
}
|
|
}
|
|
|
|
return $results;
|
|
}
|
|
|
|
/**
|
|
* Retrieves a contact.
|
|
*
|
|
* @param string $source The source name where the contact is stored
|
|
* @param string $objectId The unique id of the contact to retrieve
|
|
*
|
|
* @return array The retrieved contact.
|
|
* @throws Turba_Exception
|
|
* @throws Horde_Exception_NotFound
|
|
*/
|
|
public function getContact($source = null, $objectId = '')
|
|
{
|
|
global $cfgSources;
|
|
|
|
if (!isset($cfgSources) ||
|
|
!is_array($cfgSources) ||
|
|
!isset($cfgSources[$source])) {
|
|
return array();
|
|
}
|
|
|
|
$attributes = array();
|
|
$object = $GLOBALS['injector']->getInstance('Turba_Factory_Driver')->create($source)->getObject($objectId);
|
|
|
|
foreach (array_keys($cfgSources[$source]['map']) as $field) {
|
|
$attributes[$field] = $object->getValue($field);
|
|
}
|
|
|
|
return $attributes;
|
|
}
|
|
|
|
/**
|
|
* Retrieves a set of contacts from a single source.
|
|
*
|
|
* @param string $source The source name where the contact is stored
|
|
* @param array $objectIds The unique ids of the contact to retrieve.
|
|
*
|
|
* @return array The retrieved contact.
|
|
* @throws Turba_Exception
|
|
* @throws Horde_Exception_NotFound
|
|
*/
|
|
public function getContacts($source = '', array $objectIds = array())
|
|
{
|
|
global $cfgSources;
|
|
|
|
if (!isset($cfgSources) ||
|
|
!is_array($cfgSources) ||
|
|
!isset($cfgSources[$source])) {
|
|
return array();
|
|
}
|
|
|
|
if (!is_array($objectIds)) {
|
|
$objectIds = array($objectIds);
|
|
}
|
|
|
|
$objects = $GLOBALS['injector']->getInstance('Turba_Factory_Driver')->create($source)->getObjects($objectIds);
|
|
$results = array();
|
|
|
|
foreach ($objects as $object) {
|
|
$attributes = array();
|
|
foreach (array_keys($cfgSources[$source]['map']) as $field) {
|
|
$attributes[$field] = $object->getValue($field);
|
|
}
|
|
$results[] = $attributes;
|
|
}
|
|
|
|
return $results;
|
|
}
|
|
|
|
/**
|
|
* Retrieves a list of all possible values of a field in specified
|
|
* source(s).
|
|
*
|
|
* @param string $field Field name to check.
|
|
* @param array $sources Array containing the sources to look in.
|
|
*
|
|
* @return array An array of fields and possible values.
|
|
* @throws Turba_Exception
|
|
*/
|
|
public function getAllAttributeValues($field = '',
|
|
array $sources = array())
|
|
{
|
|
global $cfgSources;
|
|
|
|
if (!isset($cfgSources) || !is_array($cfgSources)) {
|
|
return array();
|
|
}
|
|
|
|
if (!count($sources)) {
|
|
$sources = array(Turba::getDefaultAddressbook());
|
|
}
|
|
|
|
$driver = $GLOBALS['injector']->getInstance('Turba_Factory_Driver');
|
|
$results = array();
|
|
|
|
foreach ($sources as $source) {
|
|
if (isset($cfgSources[$source])) {
|
|
$res = $driver->create($source)->search(array());
|
|
if (!($res instanceof Turba_List)) {
|
|
throw new Turba_Exception(_("Search failed"));
|
|
}
|
|
|
|
while ($ob = $res->next()) {
|
|
if ($ob->hasValue($field)) {
|
|
$results[$source . ':' . $ob->getValue('__key')] = array(
|
|
'email' => $ob->getValue('email'),
|
|
'name' => $ob->getValue('name'),
|
|
$field => $ob->getValue($field)
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return $results;
|
|
}
|
|
|
|
/**
|
|
* Retrieves a list of available time objects categories.
|
|
*
|
|
* @return array An array of all configured time object categories.
|
|
*/
|
|
public function listTimeObjectCategories()
|
|
{
|
|
$categories = array();
|
|
|
|
foreach ($GLOBALS['attributes'] as $key => $attribute) {
|
|
if (($attribute['type'] == 'monthdayyear') &&
|
|
!empty($attribute['time_object_label'])) {
|
|
foreach ($GLOBALS['cfgSources'] as $srcKey => $source) {
|
|
if (!empty($source['map'][$key])) {
|
|
$categories[$key . '/'. $srcKey] =array(
|
|
'title' => sprintf(_("%s in %s"), $attribute['time_object_label'], $source['title']),
|
|
'type' => 'share');
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return $categories;
|
|
}
|
|
|
|
/**
|
|
* Lists birthdays and/or anniversaries as time objects.
|
|
*
|
|
* @param array $time_categories The time categories (from
|
|
* listTimeObjectCategories) to list.
|
|
* @param mixed $start The start date of the period.
|
|
* @param mixed $end The end date of the period.
|
|
*
|
|
* @return array An array of timeObject results.
|
|
* @throws Turba_Exception
|
|
*/
|
|
public function listTimeObjects($time_categories, $start, $end)
|
|
{
|
|
$start = new Horde_Date($start);
|
|
$end = new Horde_Date($end);
|
|
|
|
$driver = $GLOBALS['injector']->getInstance('Turba_Factory_Driver');
|
|
$objects = array();
|
|
|
|
foreach ($time_categories as $category) {
|
|
list($category, $source) = explode('/', $category, 2);
|
|
$objects = array_merge($objects, $driver->create($source)->listTimeObjects($start, $end, $category));
|
|
}
|
|
|
|
return $objects;
|
|
}
|
|
|
|
/**
|
|
* Returns the client source name.
|
|
*
|
|
* @return string The name of the source to use with the clients api.
|
|
*/
|
|
public function getClientSource()
|
|
{
|
|
return empty($GLOBALS['conf']['client']['addressbook'])
|
|
? false
|
|
: $GLOBALS['conf']['client']['addressbook'];
|
|
}
|
|
|
|
/**
|
|
* Returns the available client fields.
|
|
*
|
|
* @return array An array describing the fields.
|
|
*/
|
|
public function clientFields()
|
|
{
|
|
return $this->fields($GLOBALS['conf']['client']['addressbook']);
|
|
}
|
|
|
|
/**
|
|
* Returns a contact from the client source.
|
|
*
|
|
* @param string $objectId Client unique ID.
|
|
*
|
|
* @return array Array of client data.
|
|
* @throws Turba_Exception
|
|
*/
|
|
public function getClient($objectId = '')
|
|
{
|
|
return $this->getContact($GLOBALS['conf']['client']['addressbook'], $objectId);
|
|
}
|
|
|
|
/**
|
|
* Returns mulitple contacts from the client source.
|
|
*
|
|
* @param array $objectIds client unique ids.
|
|
*
|
|
* @return array An array of clients data.
|
|
* @throws Turba_Exception
|
|
*/
|
|
public function getClients($objectIds = array())
|
|
{
|
|
return $this->getContacts($GLOBALS['conf']['client']['addressbook'], $objectIds);
|
|
}
|
|
|
|
/**
|
|
* Adds a client to the client source.
|
|
*
|
|
* @param array $attributes Array containing the client attributes.
|
|
*
|
|
* @return boolean
|
|
*/
|
|
public function addClient(array $attributes = array())
|
|
{
|
|
return $this->import($attributes, 'array', $this->getClientSource());
|
|
}
|
|
|
|
/**
|
|
* Updates client data.
|
|
*
|
|
* @param string $objectId The unique id of the client.
|
|
* @param array $attributes An array of client attributes.
|
|
*
|
|
* @return boolean
|
|
*/
|
|
public function updateClient($objectId = '', array $attributes = array())
|
|
{
|
|
return $this->replace($this->getClientSource() . ':' . $objectId, $attributes, 'array');
|
|
}
|
|
|
|
/**
|
|
* Deletes a client
|
|
*
|
|
* @param string $objectId The unique id of the client
|
|
*
|
|
* @return boolean
|
|
*/
|
|
public function deleteClient($objectId = '')
|
|
{
|
|
return $this->delete($this->getClientSource() . ':' . $objectId);
|
|
}
|
|
|
|
/**
|
|
* Search for clients.
|
|
*
|
|
* @param array $names The search filter values.
|
|
* @param array $fields The fields to search in.
|
|
* @param boolean $matchBegin Match word boundaries only.
|
|
*
|
|
* @return array A hash containing the search results.
|
|
* @throws Turba_Exception
|
|
*/
|
|
public function searchClients(array $names = array(),
|
|
array $fields = array(),
|
|
$matchBegin = false)
|
|
{
|
|
$abook = $this->getClientSource();
|
|
return $this->search(
|
|
$names,
|
|
array('sources' => array($abook),
|
|
'fields' => array($abook => $fields),
|
|
'matchBegin' => $matchBegin,
|
|
'forceSource' => true)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Sets the value of the specified attribute of a contact
|
|
*
|
|
* @param string|array $address Contact email address(es).
|
|
* @param string $name Contact name.
|
|
* @param string $field Field to update.
|
|
* @param string $value Field value to set.
|
|
* @param string $source Contact source.
|
|
*
|
|
* @throws Turba_Exception
|
|
*/
|
|
public function addField($address = '', $name = '', $field = '',
|
|
$value = '', $source = '')
|
|
{
|
|
if (is_array($address)) {
|
|
$e = null;
|
|
$success = 0;
|
|
|
|
foreach ($address as $tmp) {
|
|
try {
|
|
$this->addField($tmp, $name, $field, $value, $source);
|
|
++$success;
|
|
} catch (Exception $e) {}
|
|
}
|
|
|
|
if ($e) {
|
|
if ($success) {
|
|
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) . ' ' . $e->getMessage());
|
|
} else {
|
|
throw $e;
|
|
}
|
|
}
|
|
}
|
|
|
|
global $cfgSources;
|
|
|
|
if (empty($source) || !isset($cfgSources[$source])) {
|
|
throw new Turba_Exception(sprintf(_("Invalid address book: %s"), $source));
|
|
}
|
|
|
|
if (empty($address)) {
|
|
throw new Turba_Exception(_("Invalid email"));
|
|
}
|
|
|
|
if (empty($name)) {
|
|
throw new Turba_Exception(_("Invalid name"));
|
|
}
|
|
|
|
if (empty($value)) {
|
|
throw new Turba_Exception(_("Invalid entry"));
|
|
}
|
|
|
|
$driver = $GLOBALS['injector']->getInstance('Turba_Factory_Driver')->create($source);
|
|
|
|
if (!$driver->hasPermission(Horde_Perms::EDIT)) {
|
|
throw new Turba_Exception(_("Permission denied"));
|
|
}
|
|
|
|
try {
|
|
$res = $driver->search(array('email' => trim($address)), null, 'AND');
|
|
} catch (Turba_Exception $e) {
|
|
throw new Turba_Exception(sprintf(_("Search failed: %s"), $res->getMessage()));
|
|
}
|
|
|
|
if (count($res) > 1) {
|
|
try {
|
|
$res2 = $driver->search(array('email' => trim($address), 'name' => trim($name)), null, 'AND');
|
|
} catch (Turba_Exception $e) {
|
|
throw new Turba_Exception(sprintf(_("Search failed: %s"), $e->getMessage()));
|
|
}
|
|
|
|
if (!count($res2)) {
|
|
throw new Turba_Exception(sprintf(_("Multiple persons with address [%s], but none with name [%s] already exist"), trim($address), trim($name)));
|
|
}
|
|
|
|
try {
|
|
$res3 = $driver->search(array('email' => $address, 'name' => $name, $field => $value));
|
|
} catch (Turba_Exception $e) {
|
|
throw new Turba_Exception(sprintf(_("Search failed: %s"), $e->getMessage()));
|
|
}
|
|
|
|
if (count($res3)) {
|
|
throw new Turba_Exception(sprintf(_("This person already has a %s entry in the address book"), $field));
|
|
}
|
|
|
|
$ob = $res2->next();
|
|
$ob->setValue($field, $value);
|
|
$ob->store();
|
|
} elseif (count($res) == 1) {
|
|
try {
|
|
$res4 = $driver->search(array('email' => $address, $field => $value));
|
|
} catch (Turba_Exception $e) {
|
|
throw new Turba_Exception(sprintf(_("Search failed: %s"), $e->getMessage()));
|
|
}
|
|
|
|
if (count($res4)) {
|
|
throw new Turba_Exception(sprintf(_("This person already has a %s entry in the address book"), $field));
|
|
}
|
|
|
|
$ob = $res->next();
|
|
$ob->setValue($field, $value);
|
|
$ob->store();
|
|
} else {
|
|
$driver->add(array('email' => $address, 'name' => $name, $field => $value, '__owner' => $GLOBALS['registry']->getAuth()));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns a field value.
|
|
*
|
|
* @param string $address Contact email address.
|
|
* @param string $field Field to get.
|
|
* @param array $sources Sources to check.
|
|
* @param boolean $strict Match the email address strictly.
|
|
* @param boolean $multiple Return more than one entry if found and true,
|
|
* return an error if this is false.
|
|
*
|
|
* @return array An array of field value(s).
|
|
* @throws Turba_Exception
|
|
*/
|
|
public function getField($address = '', $field = '', $sources = array(),
|
|
$strict = false, $multiple = false)
|
|
{
|
|
global $cfgSources, $attributes, $injector;
|
|
|
|
if (empty($address)) {
|
|
throw new Turba_Exception(_("Invalid email"));
|
|
}
|
|
|
|
if (!isset($cfgSources) || !is_array($cfgSources)) {
|
|
return array();
|
|
}
|
|
|
|
if (!count($sources)) {
|
|
$sources = array(Turba::getDefaultAddressbook());
|
|
}
|
|
|
|
$result = array();
|
|
|
|
foreach ($sources as $source) {
|
|
if (!isset($cfgSources[$source])) {
|
|
continue;
|
|
}
|
|
$criterium = array_fill_keys(
|
|
Turba::getAvailableEmailFields($source),
|
|
$address
|
|
);
|
|
$driver = $injector->getInstance('Turba_Factory_Driver')->create($source);
|
|
try {
|
|
$list = $driver->search($criterium, null, 'OR', array(), $strict ? array_keys($criterium) : array());
|
|
} catch (Turba_Exception $e) {
|
|
Horde::log($e, 'ERR');
|
|
continue;
|
|
}
|
|
|
|
if ($list instanceof Turba_List) {
|
|
while ($ob = $list->next()) {
|
|
if ($ob->hasValue($field)) {
|
|
$result[] = $ob->getValue($field);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (count($result) > 1) {
|
|
if ($multiple) {
|
|
return $result;
|
|
} else {
|
|
throw new Turba_Exception(_("More than 1 entry found"));
|
|
}
|
|
} elseif (empty($result)) {
|
|
throw new Turba_Exception(sprintf(_("No %s entry found for %s"), $field, $address));
|
|
}
|
|
|
|
return reset($result);
|
|
}
|
|
|
|
/**
|
|
* Deletes a field value.
|
|
*
|
|
* @param string $address Contact email address.
|
|
* @param string $field Field to delete value for.
|
|
* @param array $sources Sources to delete value from.
|
|
*
|
|
* @throws Turba_Exception
|
|
*/
|
|
public function deleteField($address = '', $field = '', $sources = array())
|
|
{
|
|
global $cfgSources;
|
|
|
|
if (!strlen($address)) {
|
|
throw new Turba_Exception(_("Invalid email"));
|
|
}
|
|
|
|
if (!isset($cfgSources) || !is_array($cfgSources)) {
|
|
return;
|
|
}
|
|
|
|
if (count($sources) == 0) {
|
|
$sources = array(Turba::getDefaultAddressbook());
|
|
}
|
|
|
|
$driver = $GLOBALS['injector']->getInstance('Turba_Factory_Driver');
|
|
$success = false;
|
|
|
|
foreach ($sources as $source) {
|
|
if (isset($cfgSources[$source])) {
|
|
$sdriver = $driver->create($source);
|
|
if (!$sdriver->hasPermission(Horde_Perms::EDIT)) {
|
|
continue;
|
|
}
|
|
|
|
$res = $sdriver->search(array('email' => $address));
|
|
if ($res instanceof Turba_List) {
|
|
if (count($res) == 1) {
|
|
$ob = $res->next();
|
|
if (is_object($ob) && $ob->hasValue($field)) {
|
|
$ob->setValue($field, '');
|
|
$ob->store();
|
|
$success = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!$success) {
|
|
throw new Turba_Exception(sprintf(_("No %s entry found for %s"), $field, $address));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Obtain an array of $cfgSource entries matching the filter criteria.
|
|
*
|
|
* @param type $filter A single key -> value hash to filter the sources.
|
|
*
|
|
* @return array
|
|
*/
|
|
public function getSourcesConfig($filter = array())
|
|
{
|
|
$results = array();
|
|
|
|
if (!empty($filter)) {
|
|
foreach (Turba::availableSources() as $key => $source) {
|
|
$curr = current(array_keys($filter));
|
|
if (!empty($source[$curr]) &&
|
|
($source[$curr] == current($filter))) {
|
|
$results[$key] = $source;
|
|
}
|
|
}
|
|
}
|
|
|
|
return $results;
|
|
}
|
|
|
|
/**
|
|
* Lists all shares the current user has access to.
|
|
*
|
|
* @param integer $perms
|
|
*
|
|
* @return array of Turba_Share objects.
|
|
*/
|
|
public function listShares($perms = Horde_Perms::READ)
|
|
{
|
|
return Turba::listShares(true, $perms);
|
|
}
|
|
|
|
/**
|
|
* GroupObject API - Lists all turba lists for the current user that can
|
|
* be treated as Horde_Group objects.
|
|
*
|
|
* @return array A hash of all visible groups in the form of
|
|
* group_id => group_name
|
|
* @throws Horde_Exception
|
|
*/
|
|
public function listUserGroupObjects()
|
|
{
|
|
$groups = $owners = array();
|
|
|
|
// Only turba's SQL based sources can act as Horde_Groups
|
|
$sources = $this->getSourcesConfig(array('type' => 'sql'));
|
|
|
|
foreach ($sources as $key => $source) {
|
|
// Each source could have a different database connection
|
|
$db[$key] = empty($source['params']['sql'])
|
|
? $GLOBALS['injector']->getInstance('Horde_Db_Adapter')
|
|
: $GLOBALS['injector']->getInstance('Horde_Core_Factory_Db')->create('turba', $source['params']['sql']);
|
|
|
|
if ($source['use_shares']) {
|
|
if (empty($contact_shares)) {
|
|
$contact_shares = $this->listShares(Horde_Perms::SHOW);
|
|
}
|
|
foreach ($contact_shares as $id => $share) {
|
|
$params = @unserialize($share->get('params'));
|
|
if ($params['source'] == $key) {
|
|
$owners[] = $params['name'];
|
|
}
|
|
}
|
|
if (!$owners) {
|
|
return array();
|
|
}
|
|
} else {
|
|
$owners = array($GLOBALS['registry']->getAuth());
|
|
}
|
|
|
|
$owner_ids = array();
|
|
foreach ($owners as $owner) {
|
|
$owner_ids[] = $db[$key]->quoteString($owner);
|
|
}
|
|
|
|
$sql = 'SELECT ' . $source['map']['__key'] . ', ' . $source['map'][$source['list_name_field']]
|
|
. ' FROM ' . $source['params']['table'] . ' WHERE '
|
|
. $source['map']['__type'] . ' = \'Group\' AND '
|
|
. $source['map']['__owner'] . ' IN (' . implode(',', $owner_ids ) . ')';
|
|
|
|
try {
|
|
$results = $db[$key]->selectAssoc($sql);
|
|
} catch (Horde_Db_Exception $e) {
|
|
throw new Horde_Exception(_("Server error when performing search."));
|
|
}
|
|
|
|
foreach ($results as $id => $name) {
|
|
$groups[$key . ':' . $id] = $name;
|
|
}
|
|
}
|
|
|
|
return $groups;
|
|
}
|
|
|
|
/**
|
|
* Returns all contact groups.
|
|
*
|
|
* @return array A list of group hashes.
|
|
* @throws Turba_Exception
|
|
*/
|
|
public function getGroupObjects()
|
|
{
|
|
$ret = array();
|
|
|
|
foreach ($this->getSourcesConfig(array('type' => 'sql')) as $key => $source) {
|
|
if (empty($source['map']['__type'])) {
|
|
continue;
|
|
}
|
|
|
|
list($db, $sql) = $this->_getGroupObject($source, 'Group');
|
|
|
|
try {
|
|
$results = $db->selectAll($sql);
|
|
} catch (Horde_Db_Exception $e) {
|
|
throw new Horde_Exception(_("Server error when performing search."));
|
|
}
|
|
|
|
foreach ($results as $row) {
|
|
/* name is a reserved word in Postgresql (at a minimum). */
|
|
$row['name'] = $row['lname'];
|
|
unset($row['lname']);
|
|
$ret[$key . ':' . $row['id']] = $row;
|
|
}
|
|
}
|
|
|
|
return $ret;
|
|
}
|
|
|
|
/**
|
|
* Returns all contact groups that the specified user is a member of.
|
|
*
|
|
* @param string $user The user.
|
|
* @param boolean $parentGroups Include user as a member of the any
|
|
* parent group as well.
|
|
*
|
|
* @return array An array of group identifiers that the specified user is
|
|
* a member of.
|
|
* @throws Horde_Exception
|
|
*/
|
|
public function getGroupMemberships($user, $parentGroups = false)
|
|
{
|
|
$memberships = array();
|
|
|
|
foreach ($this->getGroupObjects() as $id => $list) {
|
|
if (in_array($user, $this->getGroupMembers($id, $parentGroups))) {
|
|
$memberships[$id] = $list['name'];
|
|
}
|
|
}
|
|
|
|
return $memberships;
|
|
}
|
|
|
|
/**
|
|
* Returns a contact group hash.
|
|
*
|
|
* @param string $gid The group identifier.
|
|
*
|
|
* @return array A hash defining the group.
|
|
* @throws Turba_Exception
|
|
*/
|
|
public function getGroupObject($gid)
|
|
{
|
|
if (empty($gid) || (strpos($gid, ':') === false)) {
|
|
throw new Turba_Exception(sprintf('Unsupported group id: %s', $gid));
|
|
}
|
|
|
|
$sources = $this->getSourcesConfig(array('type' => 'sql'));
|
|
list($source, $id) = explode(':', $gid);
|
|
if (empty($sources[$source])) {
|
|
return array();
|
|
}
|
|
|
|
list($db, $sql) = $this->_getGroupObject($sources[$source], $id);
|
|
|
|
try {
|
|
$ret = $db->selectOne($sql);
|
|
$ret['name'] = $ret['lname'];
|
|
unset($ret['lname']);
|
|
return $ret;
|
|
} catch (Horde_Db_Exception $e) {
|
|
throw new Horde_Exception(_("Server error when performing search."));
|
|
}
|
|
}
|
|
|
|
/**
|
|
*/
|
|
protected function _getGroupObject($source, $key)
|
|
{
|
|
$db = empty($source['params']['sql'])
|
|
? $GLOBALS['injector']->getInstance('Horde_Db_Adapter')
|
|
: $GLOBALS['injector']->getInstance('Horde_Core_Factory_Db')->create('turba', $source['params']['sql']);
|
|
|
|
$sql = 'SELECT ' . $source['map']['__members'] . ' members,'
|
|
. $source['map']['email'] . ' email,'
|
|
. $source['map'][$source['list_name_field']]
|
|
. ' lname FROM ' . $source['params']['table'] . ' WHERE '
|
|
. $source['map']['__key'] . ' = ' . $db->quoteString($key);
|
|
|
|
return array($db, $sql);
|
|
}
|
|
|
|
/**
|
|
* Returns a list of all members belonging to a contact group.
|
|
*
|
|
* @param string $gid The group identifier
|
|
* @param boolean $subGroups Also include members of any subgroups?
|
|
*
|
|
* @return array An array of group members (identified by email address).
|
|
* @throws Horde_Exception
|
|
*/
|
|
public function getGroupMembers($gid, $subGroups = false)
|
|
{
|
|
$contact_shares = $this->listShares(Horde_Perms::SHOW);
|
|
$sources = $this->getSourcesConfig(array('type' => 'sql'));
|
|
|
|
$entry = $this->getGroupObject($gid);
|
|
if (!$entry) {
|
|
return array();
|
|
}
|
|
list($source,) = explode(':', $gid);
|
|
$members = @unserialize($entry['members']);
|
|
if (!is_array($members)) {
|
|
return array();
|
|
}
|
|
|
|
$db[$source] = empty($sources[$source]['params']['sql'])
|
|
? $GLOBALS['injector']->getInstance('Horde_Db_Adapter')
|
|
: $GLOBALS['injector']->getInstance('Horde_Core_Factory_Db')->create('turba', $sources[$source]['params']['sql']);
|
|
|
|
$users = array();
|
|
foreach ($members as $member) {
|
|
// Is this member from the same source or a different one?
|
|
if (strpos($member, ':') !== false) {
|
|
list($newSource, $uid) = explode(':', $member);
|
|
if (!empty($contact_shares[$newSource])) {
|
|
$params = @unserialize($contact_shares[$newSource]->get('params'));
|
|
$newSource = $params['source'];
|
|
$member = $uid;
|
|
$db[$newSource] = empty($sources[$newSource]['params']['sql'])
|
|
? $GLOBALS['injector']->getInstance('Horde_Db_Adapter')
|
|
: $GLOBALS['injector']->getInstance('Horde_Core_Factory_Db')->create('turba', $sources[$newSource]['params']['sql']);
|
|
|
|
} elseif (empty($sources[$newSource])) {
|
|
// Last chance, it's not in one of our non-share sources
|
|
continue;
|
|
}
|
|
} else {
|
|
// Same source
|
|
$newSource = $source;
|
|
}
|
|
|
|
$type = $sources[$newSource]['map']['__type'];
|
|
$email = $sources[$newSource]['map']['email'];
|
|
$sql = 'SELECT ' . $email . ', ' . $type
|
|
. ' FROM ' . $sources[$newSource]['params']['table']
|
|
. ' WHERE ' . $sources[$newSource]['map']['__key']
|
|
. ' = ' . $db[$newSource]->quoteString($member);
|
|
|
|
try {
|
|
$results = $db[$newSource]->selectOne($sql);
|
|
} catch (Horde_Db_Exception $e) {
|
|
throw new Horde_Exception(_("Server error when performing search."));
|
|
}
|
|
|
|
// Sub-Lists are treated as sub groups the best that we can...
|
|
if ($subGroups && $results[$type] == 'Group') {
|
|
$users = array_merge($users, $this->getGroupMembers($newSource . ':' . $member));
|
|
}
|
|
if (strlen($results[$email])) {
|
|
// use a key to dump dups
|
|
$users[$results[$email]] = true;
|
|
}
|
|
}
|
|
|
|
ksort($users);
|
|
return array_keys($users);
|
|
}
|
|
|
|
/**
|
|
* Creates a new addressbook.
|
|
*
|
|
* @param string $name The display name for the addressbook.
|
|
* @param array $params Any addtional parameters needed.
|
|
* - synchronize: (boolean) If true, add address book to the list of
|
|
* address books to syncronize.
|
|
* DEFAULT: false (do not add to the list).
|
|
* @since 4.2.1
|
|
*
|
|
* @return string The new addressbook's id (share name).
|
|
* @since 4.2.0
|
|
*/
|
|
public function addAddressbook($name, array $params = array())
|
|
{
|
|
global $conf, $injector, $prefs;
|
|
|
|
$cfgSources = Turba::availableSources();
|
|
$driver = $injector->getInstance('Turba_Factory_Driver')
|
|
->createFromConfig($cfgSources[$conf['shares']['source']]);
|
|
$share = $driver->createShare(
|
|
strval(new Horde_Support_Randomid()),
|
|
array(
|
|
'params' => array('source' => $conf['shares']['source']),
|
|
'name' => $name
|
|
)
|
|
);
|
|
$shareName = $share->getName();
|
|
|
|
if (!empty($params['synchronize'])) {
|
|
$sync = @unserialize($prefs->getValue('sync_books'));
|
|
$sync[] = $shareName;
|
|
$prefs->setValue('sync_books', serialize($sync));
|
|
}
|
|
|
|
return $shareName;
|
|
}
|
|
|
|
/**
|
|
* Delete the specified addressbook.
|
|
*
|
|
* @param string $id The addressbook id.
|
|
* @since 4.2.0
|
|
*/
|
|
public function deleteAddressbook($id)
|
|
{
|
|
$share = $GLOBALS['injector']
|
|
->getInstance('Turba_Factory_Driver')
|
|
->create($id);
|
|
|
|
$GLOBALS['injector']
|
|
->getInstance('Turba_Shares')
|
|
->removeShare($share);
|
|
}
|
|
|
|
/**
|
|
* Update an existing addressbook's name or description.
|
|
*
|
|
* @param string $id The addressbook id.
|
|
* @param array $info The info to change:
|
|
* - name: The addressbook's display name.
|
|
* - desc: The addressbook's description.
|
|
*
|
|
* @since 4.2.0
|
|
*/
|
|
public function updateAddressbook($id, array $info)
|
|
{
|
|
$share = $GLOBALS['injector']->getInstance('Turba_Factory_Driver')->create($id);
|
|
if (!empty($info['name'])) {
|
|
$share->set('name', $info['name']);
|
|
}
|
|
if (!empty($info['desc'])) {
|
|
$share->set('desc', $info['desc']);
|
|
}
|
|
|
|
$share->save();
|
|
}
|
|
|
|
/* Helper methods. */
|
|
|
|
/**
|
|
*/
|
|
private function _modified($uid, $sources)
|
|
{
|
|
$modified = $this->getActionTimestamp($uid, 'modify', $sources);
|
|
if (empty($modified)) {
|
|
$modified = $this->getActionTimestamp($uid, 'add', $sources);
|
|
}
|
|
return $modified;
|
|
}
|
|
|
|
/**
|
|
* @throws Turba_Exception
|
|
*/
|
|
private function _getSources($sources)
|
|
{
|
|
/* Get default address book from user preferences. */
|
|
if (empty($sources)) {
|
|
$sources = @unserialize($GLOBALS['prefs']->getValue('sync_books'));
|
|
} elseif (!is_array($sources)) {
|
|
$sources = array($sources);
|
|
}
|
|
|
|
if (empty($sources)) {
|
|
$sources = array(Turba::getDefaultAddressbook());
|
|
if (empty($sources)) {
|
|
throw new Turba_Exception(_("No address book specified"));
|
|
}
|
|
}
|
|
|
|
foreach ($sources as $val) {
|
|
if (!strlen($val) || !isset($GLOBALS['cfgSources'][$val])) {
|
|
throw new Turba_Exception(sprintf(_("Invalid address book: %s"), $val));
|
|
}
|
|
}
|
|
|
|
return $sources;
|
|
}
|
|
|
|
/**
|
|
* Retrieve the list of used tag_names, tag_ids and the total number
|
|
* of resources that are linked to that tag.
|
|
*
|
|
* @param array $tags An optional array of tag_ids. If omitted, all tags
|
|
* will be included.
|
|
*
|
|
* @return array An array containing tag_name, and total
|
|
*/
|
|
public function listTagInfo($tags = null, $user = null)
|
|
{
|
|
return $GLOBALS['injector']->getInstance('Turba_Tagger')
|
|
->getTagInfo($tags, 500, null, $user);
|
|
}
|
|
|
|
/**
|
|
* SearchTags API:
|
|
* Returns an application-agnostic array (useful for when doing a tag search
|
|
* across multiple applications)
|
|
*
|
|
* The 'raw' results array can be returned instead by setting $raw = true.
|
|
*
|
|
* @param array $names An array of tag_names to search for.
|
|
* @param integer $max The maximum number of resources to return.
|
|
* @param integer $from The number of the resource to start with.
|
|
* @param string $resource_type The resource type [bookmark, '']
|
|
* @param string $user Restrict results to resources owned by $user.
|
|
* @param boolean $raw Return the raw data?
|
|
*
|
|
* @return array An array of results:
|
|
* <pre>
|
|
* 'title' - The title for this resource.
|
|
* 'desc' - A terse description of this resource.
|
|
* 'view_url' - The URL to view this resource.
|
|
* 'app' - The Horde application this resource belongs to.
|
|
* 'icon' - URL to an image.
|
|
* </pre>
|
|
*/
|
|
public function searchTags($names, $max = 10, $from = 0,
|
|
$resource_type = '', $user = null, $raw = false)
|
|
{
|
|
global $injector, $registry;
|
|
|
|
$results = $injector
|
|
->getInstance('Turba_Tagger')
|
|
->search(
|
|
$names,
|
|
array('user' => $user));
|
|
|
|
// Check for error or if we requested the raw data array.
|
|
if ($raw) {
|
|
return $results;
|
|
}
|
|
$return = array();
|
|
foreach ($results as $contact_uid) {
|
|
try {
|
|
$driver = $injector->getInstance('Turba_Factory_Driver');
|
|
foreach ($this->_getSources($sources) as $source) {
|
|
$sdriver = $driver->create($source);
|
|
if (!$sdriver->hasPermission(Horde_Perms::READ)) {
|
|
continue;
|
|
}
|
|
$result = $sdriver->search(array('__uid' => $contact_uid));
|
|
if (count($result) == 0) {
|
|
continue;
|
|
} elseif (count($result) > 1) {
|
|
throw new Turba_Exception(sprintf("Internal Horde Error: multiple Turba objects with same objectId %s.", $uid));
|
|
}
|
|
foreach ($result->objects as $obj) {
|
|
$return[] = array(
|
|
'title' => $obj->getValue('name'),
|
|
'desc' => $obj->getValue('name'),
|
|
'view_url' => $obj->url,
|
|
'app' => 'turba',
|
|
'icon' => $this->_getContactImageUrl($obj)
|
|
);
|
|
}
|
|
}
|
|
} catch (Exception $e) {
|
|
}
|
|
}
|
|
|
|
return $return;
|
|
}
|
|
|
|
|
|
protected function _getContactImageUrl($obj)
|
|
{
|
|
if ($photo = $obj->getValue('photo')) {
|
|
try {
|
|
$img = $GLOBALS['injector']->getInstance('Horde_Core_Factory_Image')->create();
|
|
$img->loadString($photo['load']['data']);
|
|
$img->resize(50, 50, true);
|
|
$data = $img->raw(true);
|
|
$type = $img->getContentType();
|
|
} catch (Horde_Image_Exception $e) {
|
|
$data = $photo['load']['data'];
|
|
$type = $obj->getValue('phototype');
|
|
}
|
|
|
|
return Horde_Url_Data::create($type, $data);
|
|
}
|
|
}
|
|
|
|
}
|