* array('name' => array('John Doe' => Turba_List, ...), ...)
*
* @throws Turba_Exception
*/
public function searchDuplicates()
{
return $this->_driver->searchDuplicates();
}
/**
* Checks if the current user has the requested permissions on this
* address book.
*
* @param integer $perm The permission to check for.
*
* @return boolean True if the user has permission, otherwise false.
*/
public function hasPermission($perm)
{
return $this->_share->hasPermission($GLOBALS['registry']->getAuth(), $perm);
}
/**
* Return the name of this address book.
*
* @string Address book name
*/
public function getName()
{
$share_parts = explode(':', $this->_share->getName());
return array_pop($share_parts);
}
/**
* Return the owner to use when searching or creating contacts in
* this address book.
*
* @return string TODO
* @throws Turba_Exception
*/
protected function _getContactOwner()
{
$params = @unserialize($this->_share->get('params'));
if (!empty($params['name'])) {
return $params['name'];
}
throw new Turba_Exception(_("Unable to find contact owner."));
}
/**
* Runs any actions after setting a new default tasklist.
*
* @param string $share The default share ID.
*/
public function setDefaultShare($share)
{
$this->_driver->setDefaultShare($share);
}
/**
* Creates an object key for a new object.
*
* @param array $attributes The attributes (in driver keys) of the
* object being added.
*
* @return string A unique ID for the new object.
*/
protected function _makeKey(array $attributes)
{
return $this->_driver->_makeKey($attributes);
}
/**
* Creates an object UID for a new object.
*
* @return string A unique ID for the new object.
*/
protected function _makeUid()
{
return $this->_driver->_makeUid();
}
/**
* Searches the address book with the given criteria and returns a
* filtered list of results. If the criteria parameter is an empty array,
* all records will be returned.
*
* @param array $criteria Array containing the search criteria.
* @param array $fields List of fields to return.
* @param array $blobFields Array of fields containing binary data.
* @param boolean $count_only Only return the count of matching entries,
* not the entries themselves.
*
* @return array Hash containing the search results.
* @throws Turba_Exception
*/
protected function _search(array $criteria, array $fields, array $blobFields = array(), $count_only = false)
{
return $this->_driver->_search($criteria, $fields, $blobFields, $count_only);
}
/**
* Reads the given data from the address book and returns the results.
*
* @param string $key The primary key field to use.
* @param mixed $ids The ids of the contacts to load.
* @param string $owner Only return contacts owned by this user.
* @param array $fields List of fields to return.
* @param array $blobFields Array of fields containing binary data.
*
* @return array Hash containing the search results.
* @throws Turba_Exception
*/
protected function _read($key, $ids, $owner, array $fields,
array $blobFields = array())
{
return $this->_driver->_read($key, $ids, $owner, $fields, $blobFields);
}
/**
* Adds the specified contact to the addressbook.
*
* @param array $attributes The attribute values of the contact.
* @param array $blob_fields TODO
*
* @throws Turba_Exception
*/
protected function _add(array $attributes, array $blob_fields = array())
{
return $this->_driver->_add($attributes, $blob_fields);
}
/**
* Returns ability of the backend to add new contacts.
*
* @return boolean Can backend add?
*/
protected function _canAdd()
{
return $this->_driver->_canAdd();
}
/**
* Deletes the specified contact from the addressbook.
*
* @param string $object_key TODO
* @param string $object_id TODO
*
* @throws Turba_Exception
*/
protected function _delete($object_key, $object_id)
{
return $this->_driver->_delete($object_key, $object_id);
}
/**
* Deletes all contacts from a specific address book.
*
* @param string $sourceName The source to remove all contacts from.
*
* @return array An array of UIDs
* @throws Turba_Exception
*/
protected function _deleteAll($sourceName = null)
{
if (is_null($sourceName)) {
$sourceName = $this->getContactOwner();
}
return $this->_driver->_deleteAll($sourceName);
}
/**
* Saves the specified object in the SQL database.
*
* @param Turba_Object $object The object to save
*
* @return string The object id, possibly updated.
* @throws Turba_Exception
*/
protected function _save(Turba_Object $object)
{
return $this->_driver->_save($object);
}
/**
* Remove all data for a specific user.
*
* @param string $user The user to remove all data for.
*/
public function removeUserData($user)
{
// Make sure we are being called by an admin.
if (!$GLOBALS['registry']->isAdmin()) {
throw new Horde_Exception_PermissionDenied(_("Permission denied"));
}
$this->_deleteAll();
$GLOBALS['injector']
->getInstance('Turba_Shares')
->removeShare($this->_share);
unset($this->_share);
}
}
turba-4.1.3/lib/Driver/Sql.php 0000664 0001750 0001750 00000060437 12233762701 014226 0 ustar jan jan
* @author Michael J. Rubinsky
* 'db' - (Horde_Db_Adapter) A DB Adapter object.
*
*/
public function __construct($name = '', array $params = array())
{
if (empty($params['db'])) {
throw new InvalidArgumentException('Missing required Horde_Db_Adapter object');
}
$this->_db = $params['db'];
unset($params['db']);
parent::__construct($name, $params);
}
/**
* Returns the number of contacts of the current user in this address book.
*
* @return integer The number of contacts that the user owns.
*/
public function count()
{
$test = $this->getContactOwner();
if (!isset($this->_countCache[$test])) {
/* Build up the full query. */
$query = 'SELECT COUNT(*) FROM ' . $this->_params['table'] .
' WHERE ' . $this->toDriver('__owner') . ' = ?';
$values = array($test);
/* Run query. */
try {
$this->_countCache[$test] = $this->_db->selectValue($query, $values);
} catch (Horde_Db_Exception $e) {
$this->_countCache[$test] = 0;
}
}
return $this->_countCache[$test];
}
/**
* Searches the SQL database with the given criteria and returns a
* filtered list of results. If the criteria parameter is an empty array,
* all records will be returned.
*
* @param array $criteria Array containing the search criteria.
* @param array $fields List of fields to return.
* @param array $blobFields TODO
* @param boolean $count_only Only return the count of matching entries,
* not the entries themselves.
*
* @return array Hash containing the search results.
* @throws Turba_Exception
*/
protected function _search(array $criteria, array $fields, array $blobFields = array(), $count_only = false)
{
return $this->_internalSearch($criteria, $fields, $blobFields, array(), $count_only);
}
/**
* Searches the SQL database with the given criteria and returns a
* filtered list of results. If the criteria parameter is an empty array,
* all records will be returned.
*
* @param array $criteria Array containing the search criteria.
* @param array $fields List of fields to return.
* @param array $blobFields TODO
* @param array $appendWhere An additional where clause to append.
* Array should contain 'sql' and 'params'
* params are used as bind parameters.
* @param boolean $count_only Only return the count of matching entries,
* not the entries themselves.
*
* @return mixed array|integer Hash containing the search results or the
* count of matching entries.
* @throws Turba_Exception
*/
protected function _internalSearch(array $criteria, array $fields, $blobFields = array(), $appendWhere = array(), $count_only = false)
{
/* Build the WHERE clause. */
$where = '';
$values = array();
if (count($criteria) || !empty($this->_params['filter'])) {
foreach ($criteria as $key => $vals) {
if ($key == 'OR' || $key == 'AND') {
if (!empty($where)) {
$where .= ' ' . $key . ' ';
}
$binds = $this->_buildSearchQuery($key, $vals);
$where .= '(' . $binds[0] . ')';
$values += $binds[1];
}
}
$where = ' WHERE ' . $where;
if (count($criteria) && !empty($this->_params['filter'])) {
$where .= ' AND ';
}
if (!empty($this->_params['filter'])) {
$where .= $this->_params['filter'];
}
if (count($appendWhere)) {
$where .= ' AND ' . $appendWhere['sql'];
$values = array_merge($values, $appendWhere['params']);
}
} elseif (count($appendWhere)) {
$where = ' WHERE ' . $appendWhere['sql'];
$values = array_merge($values, $appendWhere['params']);
}
/* Build up the full query. */
if ($count_only) {
$query = 'SELECT COUNT(*) FROM ' . $this->_params['table'] . $where;
try {
return $this->_db->selectValue($query, $values);
} catch (Horde_Db_Exception $e) {
throw new Turba_Exception($e);
}
} else {
$query = 'SELECT ' . implode(', ', $fields) . ' FROM ' . $this->_params['table'] . $where;
try {
return $this->_parseRead($blobFields, $this->_db->selectAll($query, $values));
} catch (Horde_Db_Exception $e) {
throw new Turba_Exception($e);
}
}
}
protected function _parseRead($blobFields, $result)
{
$results = array();
$columns = $this->_db->columns($this->_params['table']);
foreach ($result as $row) {
$entry = array();
foreach ($row as $field => $val) {
if (isset($blobFields[$field])) {
$entry[$field] = $columns[$field]->binaryToString($val);
} else {
$entry[$field] = $this->_convertFromDriver($val);
}
}
$results[] = $entry;
}
return $results;
}
/**
* Prepares field lists for searchDuplicates().
*
* @param array $array A list of field names.
*
* @return array A prepared list of field names.
*/
protected function _buildFields($array)
{
foreach ($array as &$entry) {
$entry = is_array($entry)
? implode(',', $this->_buildFields($entry))
: 'a1.' . $entry;
}
return $array;
}
/**
* Builds the WHERE conditions for searchDuplicates().
*
* @param array $array A list of field names.
*
* @return array A list of WHERE conditions.
*/
protected function _buildWhere($array)
{
foreach ($array as &$entry) {
if (is_array($entry)) {
$entry = reset($entry);
}
$entry = 'a1.' . $entry . ' IS NOT NULL AND a1.' . $entry . ' <> \'\'';
}
return $array;
}
/**
* Builds the JOIN conditions for searchDuplicates().
*
* @param array $array A list of field names.
*
* @return array A list of JOIN conditions.
*/
protected function _buildJoin($array)
{
foreach ($array as &$entry) {
$entry = is_array($entry)
? implode(' AND ', $this->_buildJoin($entry))
: 'a1.' . $entry . ' = a2.' . $entry;
}
return $array;
}
/**
* Searches the current address book for duplicate entries.
*
* Duplicates are determined by comparing email and name or last name and
* first name values.
*
* @return array A hash with the following format:
*
* array('name' => array('John Doe' => Turba_List, ...), ...)
*
* @throws Turba_Exception
*/
public function searchDuplicates()
{
$owner = $this->getContactOwner();
$fields = array();
if (is_array($this->map['name'])) {
if (in_array('lastname', $this->map['name']['fields']) &&
isset($this->map['lastname'])) {
$field = array($this->map['lastname']);
if (in_array('firstname', $this->map['name']['fields']) &&
isset($this->map['firstname'])) {
$field[] = $this->map['firstname'];
}
$fields[] = $field;
}
} else {
$fields[] = $this->map['name'];
}
if (isset($this->map['email'])) {
$fields[] = $this->map['email'];
}
$nameFormat = $GLOBALS['prefs']->getValue('name_format');;
if ($nameFormat != 'first_last' && $nameFormat != 'last_first') {
$nameFormat = 'first_last';
}
$order = $this->_buildFields($fields);
$joins = $this->_buildJoin($fields);
$where = $this->_buildWhere($fields);
$duplicates = array();
for ($i = 0; $i < count($joins); $i++) {
/* Build up the full query. */
$query = sprintf('SELECT DISTINCT a1.%s,%s FROM %s a1 JOIN %s a2 ON %s AND a1.%s <> a2.%s WHERE a1.%s = ? AND a2.%s = ? AND %s ORDER BY %s',
$this->map['__key'],
$order[$i],
$this->_params['table'],
$this->_params['table'],
$joins[$i],
$this->map['__key'],
$this->map['__key'],
$this->map['__owner'],
$this->map['__owner'],
$where[$i],
$order[$i]);
/* Run query. */
try {
$ids = $this->_db->selectValues($query, array($owner, $owner));
} catch (Horde_Db_Exception $e) {
throw new Turba_Exception($e);
}
$field = ($i == 0)
? 'name'
: array_search($fields[$i], $this->map);
$contacts = array();
foreach ($ids as $id) {
$contact = $this->getObject($id);
$value = $contact->getValue($field);
if ($field == 'name') {
$value = Turba::formatName($contact, $nameFormat);
}
/* HACK! */
if ($field == 'email') {
$value = Horde_String::lower($value);
}
if (!isset($contacts[$value])) {
$contacts[$value] = new Turba_List();
}
$contacts[$value]->insert($contact);
}
if ($contacts) {
$duplicates[$field] = $contacts;
}
}
return $duplicates;
}
/**
* Reads the given data from the SQL database and returns the results.
*
* @param string $key The primary key field to use.
* @param mixed $ids The ids of the contacts to load.
* @param string $owner Only return contacts owned by this user.
* @param array $fields List of fields to return.
* @param array $blobFields Array of fields containing binary data.
*
* @return array Hash containing the search results.
* @throws Turba_Exception
*/
protected function _read($key, $ids, $owner, array $fields,
array $blobFields = array())
{
$values = array();
$in = '';
if (is_array($ids)) {
if (!count($ids)) {
return array();
}
foreach ($ids as $id) {
$in .= empty($in) ? '?' : ', ?';
$values[] = $this->_convertToDriver($id);
}
$where = $key . ' IN (' . $in . ')';
} else {
$where = $key . ' = ?';
$values[] = $this->_convertToDriver($ids);
}
if (isset($this->map['__owner'])) {
$where .= ' AND ' . $this->map['__owner'] . ' = ?';
$values[] = $this->_convertToDriver($owner);
}
if (!empty($this->_params['filter'])) {
$where .= ' AND ' . $this->_params['filter'];
}
$query = 'SELECT ' . implode(', ', $fields) . ' FROM '
. $this->_params['table'] . ' WHERE ' . $where;
try {
return $this->_parseRead($blobFields, $this->_db->selectAll($query, $values));
} catch (Horde_Db_Exception $e) {
throw new Turba_Exception($e);
}
}
/**
* Adds the specified object to the SQL database.
*
* TODO
*
* @throws Turba_Exception
*/
protected function _add(array $attributes, array $blob_fields = array())
{
list($fields, $values) = $this->_prepareWrite($attributes, $blob_fields);
$query = 'INSERT INTO ' . $this->_params['table']
. ' (' . implode(', ', $fields) . ')'
. ' VALUES (' . str_repeat('?, ', count($values) - 1) . '?)';
try {
$this->_db->insert($query, $values);
} catch (Horde_Db_Exception $e) {
throw new Turba_Exception($e);
}
}
protected function _prepareWrite($attributes, $blob_fields)
{
$fields = $values = array();
foreach ($attributes as $field => $value) {
$fields[] = $field;
if (!empty($value) && isset($blob_fields[$field])) {
$values[] = new Horde_Db_Value_Binary($value);
} else {
$values[] = $this->_convertToDriver($value);
}
}
return array($fields, $values);
}
/**
* TODO
*/
protected function _canAdd()
{
return true;
}
/**
* Deletes the specified object from the SQL database.
*
* @throws Turba_Exception
*/
protected function _delete($object_key, $object_id)
{
$query = 'DELETE FROM ' . $this->_params['table'] .
' WHERE ' . $object_key . ' = ?';
$values = array($object_id);
try {
$this->_db->delete($query, $values);
} catch (Horde_Db_Exception $e) {
throw new Turba_Exception($e);
}
}
/**
* Deletes all contacts from a specific address book.
*
* @param string $sourceName The source to remove all contacts from.
*
* @return array An array of UIDs
* @throws Turba_Exception
*/
protected function _deleteAll($sourceName = null)
{
if (!$GLOBALS['registry']->getAuth()) {
throw new Turba_Exception('Permission denied');
}
/* Get owner id */
$values = empty($sourceName)
? array($GLOBALS['registry']->getAuth())
: array($sourceName);
/* Need a list of UIDs so we can notify History */
$query = 'SELECT '. $this->map['__uid'] . ' FROM '
. $this->_params['table'] . ' WHERE owner_id = ?';
try {
$ids = $this->_db->selectValues($query, $values);
} catch (Horde_Db_Exception $e) {
throw new Turba_Exception($e);
}
/* Do the deletion */
$query = 'DELETE FROM ' . $this->_params['table'] . ' WHERE owner_id = ?';
try {
$this->_db->delete($query, $values);
} catch (Horde_Db_Exception $e) {
throw new Turba_Exception($e);
}
return $ids;
}
/**
* Saves the specified object in the SQL database.
*
* @param Turba_Object $object The object to save.
*
* @return string The object id, possibly updated.
* @throws Turba_Exception
*/
function _save(Turba_Object $object)
{
list($object_key, $object_id) = each($this->toDriverKeys(array('__key' => $object->getValue('__key'))));
$attributes = $this->toDriverKeys($object->getAttributes());
$blob_fields = $this->toDriverKeys($this->getBlobs());
$where = $object_key . ' = ?';
unset($attributes[$object_key]);
list($fields, $values) = $this->_prepareWrite($attributes, $blob_fields);
$values[] = $object_id;
$query = 'UPDATE ' . $this->_params['table'] . ' SET ' . implode(' = ?, ', $fields) . ' = ? WHERE ' . $where;
try {
$this->_db->update($query, $values);
} catch (Horde_Db_Exception $e) {
throw new Turba_Exception($e);
}
return $object_id;
}
/**
* Creates an object key for a new object.
*
* @param array $attributes The attributes (in driver keys) of the
* object being added.
*
* @return string A unique ID for the new object.
*/
protected function _makeKey(array $attributes)
{
return strval(new Horde_Support_Randomid());
}
/**
* Builds a piece of a search query.
*
* @param string $glue The glue to join the criteria (OR/AND).
* @param array $criteria The array of criteria.
*
* @return array An SQL fragment and a list of values suitable for binding
* as an array.
*/
protected function _buildSearchQuery($glue, array $criteria)
{
$clause = '';
$values = array();
foreach ($criteria as $key => $vals) {
if (!empty($vals['OR']) || !empty($vals['AND'])) {
if (!empty($clause)) {
$clause .= ' ' . $glue . ' ';
}
$binds = $this->_buildSearchQuery(!empty($vals['OR']) ? 'OR' : 'AND', $vals);
$clause .= '(' . $binds[0] . ')';
$values = array_merge($values, $binds[1]);
} else {
if (isset($vals['field'])) {
if (!empty($clause)) {
$clause .= ' ' . $glue . ' ';
}
$rhs = $this->_convertToDriver($vals['test']);
$binds = $this->_db->buildClause($vals['field'], $vals['op'], $rhs, true, array('begin' => !empty($vals['begin'])));
if (is_array($binds)) {
$clause .= $binds[0];
$values = array_merge($values, $binds[1]);
} else {
$clause .= $binds;
}
} else {
foreach ($vals as $test) {
if (!empty($test['OR']) || !empty($test['AND'])) {
if (!empty($clause)) {
$clause .= ' ' . $glue . ' ';
}
$binds = $this->_buildSearchQuery(!empty($vals['OR']) ? 'OR' : 'AND', $test);
$clause .= '(' . $binds[0] . ')';
$values = array_merge($values, $binds[1]);
} else {
if (!empty($clause)) {
$clause .= ' ' . $key . ' ';
}
$rhs = $this->_convertToDriver($test['test']);
if ($rhs == '' && $test['op'] == '=') {
$clause .= '(' . $this->_db->buildClause($test['field'], '=', $rhs) . ' OR ' . $test['field'] . ' IS NULL)';
} else {
$binds = $this->_db->buildClause($test['field'], $test['op'], $rhs, true, array('begin' => !empty($test['begin'])));
if (is_array($binds)) {
$clause .= $binds[0];
$values = array_merge($values, $binds[1]);
} else {
$clause .= $binds;
}
}
}
}
}
}
}
return array($clause, $values);
}
/**
* Converts a value from the driver's charset to the default charset.
*
* @param mixed $value A value to convert.
*
* @return mixed The converted value.
*/
protected function _convertFromDriver($value)
{
return Horde_String::convertCharset($value, $this->_db->getOption('charset'), 'UTF-8');
}
/**
* Converts a value from the default charset to the driver's charset.
*
* @param mixed $value A value to convert.
*
* @return mixed The converted value.
*/
protected function _convertToDriver($value)
{
return Horde_String::convertCharset($value, 'UTF-8', $this->_db->getOption('charset'));
}
/**
* Remove all entries owned by the specified user.
*
* @param string $user The user's data to remove.
*
* @throws Horde_Exception_PermissionDenied
*/
public function removeUserData($user)
{
// Make sure we are being called by an admin.
if (!$GLOBALS['registry']->isAdmin()) {
throw new Horde_Exception_PermissionDenied(_("Permission denied"));
}
$this->_deleteAll($user);
}
/**
* Obtain Turba_List of items to get TimeObjects out of.
*
* @param Horde_Date $start The starting date.
* @param Horde_Date $end The ending date.
* @param string $field The address book field containing the
* timeObject information (birthday, anniversary)
*
* @return Turba_List Object list.
* @throws Turba_Exception
*/
public function getTimeObjectTurbaList(Horde_Date $start, Horde_Date $end, $field)
{
$t_object = $this->toDriver($field);
$criteria = $this->makesearch(
array('__owner' => $this->getContactOwner()),
'AND',
array($this->toDriver('__owner') => true),
false);
// Limit to entries that actually contain a birthday and that are in the
// date range we are looking for.
$criteria['AND'][] = array(
'field' => $t_object,
'op' => '<>',
'test' => ''
);
if ($start->year == $end->year) {
$start = sprintf('%02d-%02d', $start->month, $start->mday);
$end = sprintf('%02d-%02d', $end->month, $end->mday);
$where = array('sql' => $t_object . ' IS NOT NULL AND SUBSTR('
. $t_object . ', 6, 5) BETWEEN ? AND ?',
'params' => array($start, $end));
} else {
$months = array();
$diff = ($end->month + 12) - $start->month;
$newDate = new Horde_Date(array(
'month' => $start->month,
'mday' => $start->mday,
'year' => $start->year
));
for ($i = 0; $i <= $diff; ++$i) {
$months[] = sprintf('%02d', $newDate->month++);
}
$where = array('sql' => $t_object . ' IS NOT NULL AND SUBSTR('
. $t_object . ', 6, 2) IN ('
. str_repeat('?,', count($months) - 1) . '?)',
'params' => $months);
}
$fields_pre = array(
'__key', '__type', '__owner', 'name', 'birthday', 'category',
'anniversary'
);
$fields = array();
foreach ($fields_pre as $field) {
$result = $this->toDriver($field);
if (is_array($result)) {
foreach ($result as $composite_field) {
$composite_result = $this->toDriver($composite_field);
if ($composite_result) {
$fields[] = $composite_result;
}
}
} elseif ($result) {
$fields[] = $result;
}
}
return $this->_toTurbaObjects($this->_internalSearch($criteria, $fields, array(), $where));
}
}
turba-4.1.3/lib/Driver/Vbook.php 0000664 0001750 0001750 00000010757 12233762701 014547 0 ustar jan jan
* @category Horde
* @license http://www.horde.org/licenses/apache ASL
* @package Turba
*/
class Turba_Driver_Vbook extends Turba_Driver
{
/**
* Search type for this virtual address book.
*
* @var string
*/
public $searchType;
/**
* The search criteria that defines this virtual address book.
*
* @var array
*/
public $searchCriteria;
/**
*
* @see Turba_Driver::__construct
* @throws Turba_Exception
*/
public function __construct($name = '', array $params = array())
{
parent::__construct($name, $params);
/* Grab a reference to the share for this vbook. */
$this->_share = $this->_params['share'];
/* Load the underlying driver. */
$this->_driver = $GLOBALS['injector']->getInstance('Turba_Factory_Driver')->create($this->_params['source']);
$this->searchCriteria = empty($this->_params['criteria'])
? array()
: $this->_params['criteria'];
$this->searchType = (count($this->searchCriteria) > 1)
? 'advanced'
: 'basic';
}
/**
* Return the owner to use when searching or creating contacts in
* this address book.
*
* @return string
*/
protected function _getContactOwner()
{
return $this->_driver->getContactOwner();
}
/**
* Return all entries matching the combined searches represented by
* $criteria and the vitural address book's search criteria.
*
* @param array $criteria Array containing the search criteria.
* @param array $fields List of fields to return
* @param array $blobFileds Array of fields that contain
*
* @return array Hash containing the search results.
* @throws Turba_Exception
*/
protected function _search(array $criteria, array $fields, array $blobFields = array(), $count_only = false)
{
/* Add the passed in search criteria to the vbook criteria
* (which need to be mapped from turba fields to
* driver-specific fields). */
$criteria['AND'][] = $this->makeSearch($this->searchCriteria, 'AND', array());
$results = $this->_driver->_search($criteria, $fields, $blobFields);
return $count_only ? count($results) : $results;
}
/**
* Reads the requested entries from the underlying source.
*
* @param string $key The primary key field to use.
* @param mixed $ids The ids of the contacts to load.
* @param string $owner Only return contacts owned by this user.
* @param array $fields List of fields to return.
* @param array $blobFields Array of fields containing binary data.
*
* @return array Hash containing the search results.
* @throws Turba_Exception
*/
protected function _read($key, $ids, $owner, array $fields,
array $blobFields = array())
{
return $this->_driver->_read($key, $ids, $owner, $fields, $blobFields);
}
/**
* Adds the specified contact to the addressbook.
*
* @param array $attributes The attribute values of the contact.
* @param array $blob_fields TODO
*
* @throws Turba_Exception
*/
protected function _add(array $attributes, array $blob_fields = array())
{
throw new Turba_Exception(_("You cannot add new contacts to a virtual address book"));
}
/**
* Not supported for virtual address books.
*
* @see Turba_Driver::_delete
* @throws Turba_Exception
*/
protected function _delete($object_key, $object_id)
{
throw new Turba_Exception(_("You cannot delete contacts from a virtual address book"));
}
/**
* @see Turba_Driver::_save
*/
protected function _save(Turba_Object $object)
{
return $this->_driver->save($object);
}
/**
* Check to see if the currently logged in user has requested permissions.
*
* @param integer $perm The permissions to check against.
*
* @return boolean True or False.
*/
public function hasPermission($perm)
{
return $this->_driver->hasPermission($perm);
}
}
turba-4.1.3/lib/Exception/NotSupported.php 0000664 0001750 0001750 00000000115 12233762701 016623 0 ustar jan jan
* @category Horde
* @license http://www.horde.org/licenses/apache ASL
* @package Turba
*/
class Turba_Exception_ObjectExists extends Turba_Exception
{
}
turba-4.1.3/lib/Factory/Driver.php 0000664 0001750 0001750 00000011055 12233762701 015066 0 ustar jan jan
* @category Horde
* @license http://www.horde.org/licenses/apl.html APL
* @link http://pear.horde.org/index.php?package=Turba
* @package Turba
*/
/**
* A Horde_Injector:: based Turba_Driver:: factory.
*
* Copyright 2010-2013 Horde LLC (http://www.horde.org/)
*
* See the enclosed file COPYING for license information (APL). If you
* did not receive this file, see http://www.horde.org/licenses/apl.html.
*
* @author Michael Slusarz ' . _("No matching contacts") . '
'; } } /** * Renders the list contents into an HTML view. * * @param integer $numDisplayed Ouptut parameter - the number of rows * rendered. * @param integer $min Minimum number of rows to display. * @param integer $max Maximum number of rows to display. * @param string $page The currently displayed page. * * @return string HTML to echo. */ public function getPage(&$numDisplayed, $min = 0, $max = null, $page = 0) { if (is_null($max)) { $max = count($this); } return $this->_get($numDisplayed, new Turba_View_List_PageFilter($min, $max), $page); } /** * Renders the list contents that match $alpha into an HTML view. * * @param integer $numDisplayed This will be set to the number of contacts * in the view. * @param string $alpha The letter to display. * * @return string HTML of the list. */ public function getAlpha(&$numDisplayed, $alpha) { return $this->_get($numDisplayed, new Turba_View_List_AlphaFilter($alpha), $alpha); } /** * Retrieves a column's name * * @param integer $i The zero-basd index of the column * * @return string */ public function getColumnName($i) { return Turba::getColumnName($i, $this->columns); } /** * @param integer $i The zero-based index of the column */ public function getSortInfoForColumn($i) { $sortorder = Turba::getPreferredSortOrder(); $column_name = $this->getColumnName($i); $i = 0; foreach ($sortorder as $sortfield) { if ($column_name == $sortfield['field']) { return array_merge($sortfield, array('rank' => $i)); } $i++; } return null; } /** * @param integer $i * @param string $title * * @return string */ public function getColumnSortImage($i, $title = null) { if (is_null($title)) { $title = _("Sort Direction"); } $sortdir = $this->getColumnSortDirection($i); if ($this->isPrimarySortColumn($i)) { return Horde::img($sortdir ? 'za.png' : 'az.png', $title); } else { return Horde::img($sortdir ? 'za_secondary.png' : 'az_secondary.png', _("Sort Direction")); } } /** * Retrieves a natural language description of the sort order * * @return string */ public function getSortOrderDescription() { $description = array(); $sortorder = Turba::getPreferredSortOrder(); foreach ($sortorder as $elt) { $field = $elt['field']; if (!strlen($field) || ($field == 'lastname')) { $field = 'name'; } $description[] = $GLOBALS['attributes'][$field]['label']; } return join(', ', $description); } /** * @param integer $i The zero-based index of the column */ public function getColumnSortDirection($i) { $result = $this->getSortInfoForColumn($i); if (is_null($result)) { return null; } return $result['ascending'] ? 0 : 1; } /** * Determines whether we are sorting on the specified column * * @param integer $i The zero-based column index * * @return boolean */ public function isSortColumn($i) { return !is_null($this->getSortInfoForColumn($i)); } /** * Determines whether this is the first column to sort by * * @param integer $i The zero-based column index * * @return boolean */ public function isPrimarySortColumn($i) { $result = $this->getSortInfoForColumn($i); if (is_null($result)) { return false; } return ($result['rank'] == 0); } /** * @param integer $numDisplayed Set to the number of displayed contacts. * @param object $filter A Turba_View_List filter object. * @param string $page The currently displayed page. * * @return string */ protected function _get(&$numDisplayed, $filter, $page) { ob_start(); $width = floor(90 / (count($this->columns) + 1)); $own = $GLOBALS['prefs']->getValue('own_contact'); if (strpos($own, ';')) { list($own_source, $own_id) = explode(';', $own); } else { $own_source = $own_id = null; } $vars = Horde_Variables::getDefaultVariables(); include TURBA_TEMPLATES . '/browse/column_headers.inc'; $numDisplayed = 0; $this->list->reset(); while ($ob = $this->list->next()) { if ($filter->skip($ob)) { continue; } include TURBA_TEMPLATES . '/browse/row.inc'; $numDisplayed++; } include TURBA_TEMPLATES . '/browse/column_footers.inc'; return ob_get_clean(); } /** */ public function getAddSources() { global $addSources; // Create list of lists for Add to. $addToList = array(); $addToListSources = array(); foreach ($addSources as $src => $srcConfig) { if (!empty($srcConfig['map']['__type'])) { $addToListSources[] = array('key' => '', 'name' => ' ' . htmlspecialchars($srcConfig['title']), 'source' => htmlspecialchars($src)); $srcDriver = $GLOBALS['injector']->getInstance('Turba_Factory_Driver')->create($src); try { $listList = $srcDriver->search( array('__type' => 'Group'), array( array( 'field' => 'name', 'ascending' => true ) ), 'AND', array('name') ); $listList->reset(); $currentList = Horde_Util::getFormData('key'); while ($listObject = $listList->next()) { if ($listObject->getValue('__key') != $currentList) { $addToList[] = array( 'name' => htmlspecialchars($listObject->getValue('name')), 'source' => htmlspecialchars($src), 'key' => htmlspecialchars($listObject->getValue('__key')) ); } } } catch (Turba_Exception $e) { $GLOBALS['notification']->push($e, 'horde.error'); } } } if ($addToListSources) { if ($addToList) { array_unshift($addToList, '- - - - - - - - -'); } $addToList = array_merge(array(_("Create a new Contact List in:")), $addToListSources, $addToList); $addToListSources = null; } return array($addToList, $addToListSources); } /* Countable methods. */ /** * Returns the number of Turba_Objects that are in the list. Use this to * hide internal implementation details from client objects. * * @return integer The number of objects in the list. */ public function count() { return $this->list->count(); } } turba-4.1.3/lib/.htaccess 0000644 0001750 0001750 00000000016 12233762701 013302 0 ustar jan jan Deny from all turba-4.1.3/lib/Api.php 0000664 0001750 0001750 00000224460 12233762701 012743 0 ustar jan jan * @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, $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' => true ); foreach (Turba::listShares() as $share) { $owners[$share->get('owner') ? $share->get('owner') : '-system-'] = true; } $now = time(); foreach (array_keys($owners) as $owner) { if (in_array('name', $properties)) { $results['turba/' . $owner]['name'] = $owner; } if (in_array('icon', $properties)) { $results['turba/' . $owner]['icon'] = Horde_Themes::img('turba.png'); } if (in_array('browseable', $properties)) { $results['turba/' . $owner]['browseable'] = true; } if (in_array('contenttype', $properties)) { $results['turba/' . $owner]['contenttype'] = 'httpd/unix-directory'; } if (in_array('contentlength', $properties)) { $results['turba/' . $owner]['contentlength'] = 0; } if (in_array('modified', $properties)) { // @TODO: Get a real modification date $results['turba/' . $owner]['modified'] = $now; } if (in_array('created', $properties)) { // @TODO Get a real creation date $results['turba/' . $owner]['created'] = 0; } } 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($parts[0], array( 'attributes' => $parts[0], 'perm' => Horde_Perms::READ )); /* The last check returns all addressbooks for the requested * user, but that does not mean the requesting user has access * to them. * Filter out those address books for which the requesting * user has no access. */ $addressbooks = Turba::permissionsFilter($addressbooks); } $curpath = 'turba/' . $parts[0] . '/'; $now = time(); foreach ($addressbooks as $addressbook => $info) { if (in_array('name', $properties)) { $results[$curpath . $addressbook]['name'] = sprintf( _("Contacts from %s"), ($info instanceof Horde_Share_Object) ? $info->get('name') : $info['title'] ); } 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('contenttype', $properties)) { $results[$curpath . $addressbook]['contenttype'] = 'httpd/unix-directory'; } if (in_array('contentlength', $properties)) { $results[$curpath . $addressbook]['contentlength'] = 0; } if (in_array('modified', $properties)) { // @TODO: Get a real modification date $results[$curpath . $addressbook]['modified'] = $now; } if (in_array('created', $properties)) { // @TODO Get a real creation date $results[$curpath . $addressbook]['created'] = 0; } } 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(); } $contacts = $injector->getInstance('Turba_Factory_Driver')->create($parts[1])->search(array()); $contacts->reset(); $curpath = 'turba/' . $parts[0] . '/' . $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('icon', $properties)) { $results[$key]['icon'] = Horde_Themes::img('mime/vcard.png'); } if (in_array('browseable', $properties)) { $results[$key]['browseable'] = false; } if (in_array('contenttype', $properties)) { $results[$key]['contenttype'] = 'text/x-vcard'; } if (in_array('contentlength', $properties)) { try { $data = $this->export($contact->getValue('__uid'), 'text/x-vcard', $contact->getSource()); } catch (Turba_Exception $e) { $data = ''; } $results[$key]['contentlength'] = strlen($data); } if (in_array('modified', $properties)) { $results[$key]['modified'] = $this->_modified($contact->getValue('__uid'), $parts[1]); } 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' => $this->export($contact->getValue('__uid'), 'text/x-vcard', $contact->getSource()), '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 * * @return array A hash with 'add', 'modify' and 'delete' arrays. */ public function getChanges($start, $end, $isModSeq = false) { return array( 'add' => $this->listBy('add', $start, null, $end, $isModSeq), 'modify' => $this->listBy('modify', $start, null, $end, $isModSeq), 'delete' => $this->listBy('delete', $start, null, $end, $isModSeq) ); } /** * Return all changes occuring between the specified modification * sequences. * * @param integer $start The starting modseq. * @param integer $end The ending modseq. * * @return array The changes @see getChanges() * @since 4.1.1 */ public function getChangesByModSeq($start, $end) { return $this->getChanges($start, $end, true); } /** * 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. * * @return integer The modseq. * @since 4.1.1 */ public function getHighestModSeq() { return $GLOBALS['injector']->getInstance('Horde_History')->getHighestModSeq('turba'); } /** * 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. * * @return string The new UID. * * @throws Turba_Exception * @throws Turba_Exception_ObjectExists */ public function import($content, $contentType = 'array', $source = null) { 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")); } // Create a category manager. $cManager = new Horde_Prefs_CategoryManager(); $categories = $cManager->get(); 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 = $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; } $result = $driver->add($content); if (!empty($content['category']) && !in_array($content['category'], $categories)) { $cManager->add($content['category']); $categories[] = $content['category']; } $ids[] = $result; } } return $ids; } break; default: throw new Turba_Exception(sprintf(_("Unsupported Content-Type: %s"), $contentType)); } } if ($content instanceof Horde_Icalendar_Vcard) { $content = $driver->toHash($content); } // Check if the entry already exists in the data source. $result = $driver->search($content); 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. if (Horde::hookExists('encode_attribute', 'turba')) { foreach ($content as $attribute => &$value) { try { $value = Horde::callHook('encode_attribute', array($attribute, $value, null, null), 'turba'); } catch (Turba_Exception $e) {} } } $result = $driver->add($content); if (!empty($content['category']) && !in_array($content['category'], $categories)) { $cManager->add($content['category']); } 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? Either * a string with one of: - text/directory - * text/vcard - text/x-vcard The first two * produce a vcard3.0 (rfc2426), the second * produces a vcard in old 2.1 format * defined by imc.org Also supports a raw * array * @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. * * @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); /* 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. * 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). * * @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()); } $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; } $sdriver = $driver->create($source); foreach ($names as $name) { $trimname = trim($name); $out = $criteria = array(); if (strlen($trimname)) { if (isset($opts['fields'][$source])) { foreach ($opts['fields'][$source] as $field) { $criteria[$field] = $trimname; } } if (!count($criteria)) { $criteria['name'] = $trimname; } } $search = $sdriver->search( $criteria, Turba::getPreferredSortOrder(), 'OR', $opts['returnFields'], $opts['customStrict'], $opts['matchBegin'], $opts['count_only'] ); 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 ); } 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) { $att[$key] = $ob->getValue($key); } $email = new Horde_Mail_Rfc822_List(); foreach (array_keys($att) as $key) { if ($ob->getValue($key) && isset($attributes[$key]) && ($attributes[$key]['type'] == 'email')) { // Multiple addresses support $email->add($rfc822->parseAddressList($ob->getValue($key), array( 'limit' => (isset($attributes[$key]['params']) && is_array($attributes[$key]['params']) && !empty($attributes[$key]['params']['allow_multi'])) ? 0 : 1 ))); } } $display_name = ($ob->hasValue('name') || !isset($ob->driver->alternativeName)) ? Turba::formatName($ob) : $ob->getValue($ob->driver->alternativeName); 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) { $out[] = array_merge($att, array( '__type' => 'Object', 'email' => $val, 'id' => $att['__key'], 'name' => $display_name, 'source' => $source )); } } } } 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 = $GLOBALS['conf']['client']['addressbook']; 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; if (empty($address)) { throw new Turba_Exception(_("Invalid email")); } if (!isset($cfgSources) || !is_array($cfgSources)) { return array(); } if (!count($sources)) { $sources = array(Turba::getDefaultAddressbook()); } $driver = $GLOBALS['injector']->getInstance('Turba_Factory_Driver'); $result = array(); foreach ($sources as $source) { if (!isset($cfgSources[$source])) { continue; } $sdriver = $driver->create($source); $criterium = array('email' => $address); if (!isset($sdriver->map['email'])) { if (isset($sdriver->map['emails'])) { $criterium = array('emails' => $address); } else { continue; } } try { $list = $sdriver->search($criterium, null, 'AND', array(), $strict ? array('email') : array()); } catch (Turba_Exception $e) { Horde::logMessage($e, 'ERR'); } 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(); $turba_sources = Horde::loadConfiguration('backends.php', 'cfgSources', 'turba'); foreach ($turba_sources as $key => $source) { if (!empty($filter)) { if (!empty($source[current(array_keys($filter))]) && $source[current(array_keys($filter))] == 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) { Horde::logMessage($e); throw new Horde_Exception_Wrapped($e); } 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) { Horde::logMessage($e); throw new Turba_Exception($e); } 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) { Horde::logMessage($e); throw new Turba_Exception($e); } } /** */ 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) { Horde::logMessage($e); throw new Horde_Exception_Wrapped($e); } // 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); } /* 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; } } turba-4.1.3/lib/Application.php 0000664 0001750 0001750 00000074704 12233762701 014501 0 ustar jan jan true, 'modseq' => true, ); /** */ public $version = 'H5 (4.1.3)'; /** */ protected function _bootstrap() { /* Add Turba-specific factories. */ $factories = array( 'Turba_Shares' => 'Turba_Factory_Shares' ); foreach ($factories as $key => $val) { $GLOBALS['injector']->bindFactory($key, $val, 'create'); } } /** * Global variables defined: * $addSources - TODO * $attributes - (array) Attribute data from the config/attributes.php * file. * $browse_source_count - TODO * $cfgSources - TODO * $copymoveSources - TODO */ protected function _init() { // Turba source and attribute configuration. $attributes = Horde::loadConfiguration('attributes.php', 'attributes', 'turba'); $cfgSources = Turba::availableSources(); /* UGLY UGLY UGLY - we should NOT be using this as a global * variable all over the place. */ $GLOBALS['cfgSources'] = &$cfgSources; // See if any of our sources are configured to use Horde_Share. foreach ($cfgSources as $key => $cfg) { if (!empty($cfg['use_shares'])) { // Create a share instance. $GLOBALS['session']->set('turba', 'has_share', true); $cfgSources = Turba::getConfigFromShares($cfgSources); break; } } $GLOBALS['attributes'] = $attributes; $cfgSources = Turba::permissionsFilter($cfgSources); // Build the directory sources select widget. if (empty(Turba::$source)) { if (!(Turba::$source = $GLOBALS['session']->get('turba', 'source'))) { Turba::$source = Turba::getDefaultAddressbook(); } Turba::$source = Horde_Util::getFormData('source', Turba::$source); } $GLOBALS['browse_source_count'] = 0; foreach (Turba::getAddressBooks() as $key => $curSource) { if (!empty($curSource['browse'])) { $GLOBALS['browse_source_count']++; if (empty(Turba::$source)) { Turba::$source = $key; } } } if (empty($cfgSources[Turba::$source]['browse'])) { Turba::$source = Turba::getDefaultAddressbook(); } $GLOBALS['session']->set('turba', 'source', Turba::$source); $GLOBALS['addSources'] = Turba::getAddressBooks(Horde_Perms::EDIT, array('require_add' => true)); $GLOBALS['copymoveSources'] = $GLOBALS['addSources']; unset($GLOBALS['copymoveSources'][Turba::$source]); } /** */ public function perms() { $cfgSources = Turba::availableSources(); $perms = array( 'sources' => array( 'title' => _("Sources") ) ); // Run through every contact source. foreach ($cfgSources as $source => $curSource) { $perms['sources:' . $source] = array( 'title' => $curSource['title'] ); $perms['sources:' . $source . ':max_contacts'] = array( 'title' => _("Maximum Number of Contacts"), 'type' => 'int' ); } return $perms; } /** */ public function menu($menu) { if ($GLOBALS['browse_source_count']) { $menu->add(Horde::url('browse.php'), _("_Browse"), 'turba-browse', null, null, null, (($GLOBALS['prefs']->getValue('initial_page') == 'browse.php' && basename($_SERVER['PHP_SELF']) == 'index.php' && basename(dirname($_SERVER['PHP_SELF'])) != 'addressbooks') || (basename($_SERVER['PHP_SELF']) == 'browse.php' && Horde_Util::getFormData('key') != '**search')) ? 'current' : '__noselection'); } $menu->add(Horde::url('search.php'), _("_Search"), 'turba-search', null, null, null, (($GLOBALS['prefs']->getValue('initial_page') == 'search.php' && basename($_SERVER['PHP_SELF']) == 'index.php' && strpos($_SERVER['PHP_SELF'], 'addressbooks/index.php') === false) || (basename($_SERVER['PHP_SELF']) == 'browse.php' && Horde_Util::getFormData('key') == '**search')) ? 'current' : null); /* Import/Export */ if ($GLOBALS['conf']['menu']['import_export']) { $menu->add(Horde::url('data.php'), _("_Import/Export"), 'horde-data'); } } /** * Add additional items to the sidebar. * * @param Horde_View_Sidebar $sidebar The sidebar object. */ public function sidebar($sidebar) { if (count($GLOBALS['addSources'])) { $sidebar->addNewButton(_("_New Contact"), Horde::url('add.php')); } $user = $GLOBALS['registry']->getAuth(); $url = Horde::url(''); $edit = Horde::url('addressbooks/edit.php'); $sidebar->containers['my'] = array( 'header' => array( 'id' => 'turba-toggle-my', 'label' => _("My Address Books"), 'collapsed' => false, ), ); if ($GLOBALS['registry']->getAuth() && $GLOBALS['session']->get('turba', 'has_share')) { $sidebar->containers['my']['header']['add'] = array( 'url' => Horde::url('addressbooks/create.php'), 'label' => _("Create a new Address Book"), ); } $sidebar->containers['shared'] = array( 'header' => array( 'id' => 'turba-toggle-shared', 'label' => _("Shared Address Books"), 'collapsed' => true, ), ); $shares = array(); foreach (Turba::listShares() as $id => $abook) { $row = array( 'selected' => $id == Turba::$source, 'url' => $url->add('source', $id), 'label' => $abook->get('name'), 'edit' => $edit->add('a', $abook->getName()), 'type' => 'radiobox', ); if ($abook->get('owner') && $abook->get('owner') == $user) { $sidebar->addRow($row, 'my'); if ($row['selected']) { $sidebar->containers['my']['header']['collapsed'] = false; } } else { if ($abook->get('owner')) { $row['label'] .= ' [' . $GLOBALS['registry']->convertUsername($abook->get('owner'), false) . ']'; } $sidebar->addRow($row, 'shared'); if ($row['selected']) { $sidebar->containers['shared']['header']['collapsed'] = false; } } $shares[$id] = true; } $sidebar->containers['other'] = array( 'header' => array( 'id' => 'turba-toggle-other', 'label' => _("Other Address Books"), 'collapsed' => true, ), ); foreach (Turba::getAddressBooks(Horde_Perms::READ) as $id => $abook) { if (isset($shares[$id])) { continue; } $row = array( 'selected' => $id == Turba::$source, 'url' => $url->add('source', $id), 'label' => $abook['title'], 'type' => 'radiobox', ); $sidebar->addRow($row, 'other'); if ($row['selected']) { $sidebar->containers['other']['header']['collapsed'] = false; } } } /** * Returns values for
* array('name' => array('John Doe' => Turba_List, ...), ...)
*
* @throws Turba_Exception
*/
public function searchDuplicates()
{
return array();
}
/**
* Takes an array of object hashes and returns a Turba_List
* containing the correct Turba_Objects
*
* @param array $objects An array of object hashes (keyed to backend).
* @param array $sort_order Array of hashes describing sort fields. Each
* hash has the following fields:
*
* ascending - (boolean) Indicating sort direction.
* field - (string) Sort field.
*
*
* @return Turba_List A list object.
*/
protected function _toTurbaObjects(array $objects, array $sort_order = null)
{
$list = new Turba_List();
foreach ($objects as $object) {
/* Translate the driver-specific fields in the result back to the
* more generalized common Turba attributes using the map. */
$object = $this->toTurbaKeys($object);
$done = false;
if (!empty($object['__type']) &&
ucwords($object['__type']) != 'Object') {
$class = 'Turba_Object_' . ucwords($object['__type']);
if (class_exists($class)) {
$list->insert(new $class($this, $object));
$done = true;
}
}
if (!$done) {
$list->insert(new Turba_Object($this, $object));
}
}
$list->sort($sort_order);
/* Return the filtered (sorted) results. */
return $list;
}
/**
* Returns a list of birthday or anniversary hashes from this source for a
* certain period.
*
* @param Horde_Date $start The start date of the valid period.
* @param Horde_Date $end The end date of the valid period.
* @param string $category The timeObjects category to return.
*
* @return array A list of timeObject hashes.
* @throws Turba Exception
*/
public function listTimeObjects(Horde_Date $start, Horde_Date $end, $category)
{
try {
$res = $this->getTimeObjectTurbaList($start, $end, $category);
} catch (Turba_Exception $e) {
/* Try the default implementation before returning an error */
$res = $this->_getTimeObjectTurbaListFallback($start, $end, $category);
}
$t_objects = array();
while ($ob = $res->next()) {
$t_object = $ob->getValue($category);
if (empty($t_object) ||
$t_object == '0000-00-00' ||
!preg_match('/(\d{4})-(\d{2})-(\d{2})/', $t_object, $match)) {
continue;
}
$t_object = new Horde_Date(array(
'mday' => $match[3],
'month' => $match[2],
'year' => $match[1]
));
if ($t_object->compareDate($end) > 0) {
continue;
}
$t_object_end = new Horde_Date($t_object);
++$t_object_end->mday;
$key = $ob->getValue('__key');
// Calculate the age of the time object
if ($start->year == $end->year ||
$end->year == 9999) {
$age = $start->year - $t_object->year;
} elseif ($t_object->month <= $end->month) {
// t_object must be in later year
$age = $end->year - $t_object->year;
} else {
// t_object must be in earlier year
$age = $start->year - $t_object->year;
}
$title = sprintf(_("%d. %s of %s"),
$age,
$GLOBALS['attributes'][$category]['label'],
$ob->getValue('name'));
$t_objects[] = array(
'id' => $key,
'title' => $title,
'start' => sprintf('%d-%02d-%02dT00:00:00',
$t_object->year,
$t_object->month,
$t_object->mday),
'end' => sprintf('%d-%02d-%02dT00:00:00',
$t_object_end->year,
$t_object_end->month,
$t_object_end->mday),
'category' => $ob->getValue('category'),
'recurrence' => array('type' => Horde_Date_Recurrence::RECUR_YEARLY_DATE,
'interval' => 1),
'params' => array('source' => $this->_name, 'key' => $key),
'link' => Horde::url('contact.php', true)->add(array('source' => $this->_name, 'key' => $key))->setRaw(true)
);
}
return $t_objects;
}
/**
* Default implementation for obtaining a Turba_List to get TimeObjects
* out of.
*
* @param Horde_Date $start The starting date.
* @param Horde_Date $end The ending date.
* @param string $field The address book field containing the
* timeObject information (birthday,
* anniversary).
*
* @return Turba_List A list of objects.
* @throws Turba_Exception
*/
public function getTimeObjectTurbaList(Horde_Date $start, Horde_Date $end, $field)
{
return $this->_getTimeObjectTurbaListFallback($start, $end, $field);
}
/**
* Default implementation for obtaining a Turba_List to get TimeObjects
* out of.
*
* @param Horde_Date $start The starting date.
* @param Horde_Date $end The ending date.
* @param string $field The address book field containing the
* timeObject information (birthday,
* anniversary).
*
* @return Turba_List A list of objects.
* @throws Turba_Exception
*/
protected function _getTimeObjectTurbaListFallback(Horde_Date $start, Horde_Date $end, $field)
{
return $this->search(array(), null, 'AND', array('name', $field, 'category'));
}
/**
* Retrieves a set of objects from the source.
*
* @param array $objectIds The unique ids of the objects to retrieve.
*
* @return array The array of retrieved objects (Turba_Objects).
* @throws Turba_Exception
* @throws Horde_Exception_NotFound
*/
public function getObjects(array $objectIds)
{
$objects = $this->_read($this->map['__key'], $objectIds,
$this->getContactOwner(),
array_values($this->fields),
$this->toDriverKeys($this->getBlobs()));
if (!is_array($objects)) {
throw new Horde_Exception_NotFound();
}
$results = array();
foreach ($objects as $object) {
$object = $this->toTurbaKeys($object);
$done = false;
if (!empty($object['__type']) &&
ucwords($object['__type']) != 'Object') {
$class = 'Turba_Object_' . ucwords($object['__type']);
if (class_exists($class)) {
$results[] = new $class($this, $object);
$done = true;
}
}
if (!$done) {
$results[] = new Turba_Object($this, $object);
}
}
return $results;
}
/**
* Retrieves one object from the source.
*
* @param string $objectId The unique id of the object to retrieve.
*
* @return Turba_Object The retrieved object.
* @throws Turba_Exception
* @throws Horde_Exception_NotFound
*/
public function getObject($objectId)
{
$result = $this->getObjects(array($objectId));
if (empty($result[0])) {
throw new Horde_Exception_NotFound();
}
$result = $result[0];
if (!isset($this->map['__owner'])) {
$result->attributes['__owner'] = $this->getContactOwner();
}
return $result;
}
/**
* Adds a new entry to the contact source.
*
* @param array $attributes The attributes of the new object to add.
*
* @return string The new __key value on success.
* @throws Turba_Exception
*/
public function add(array $attributes)
{
/* Only set __type and __owner if they are not already set. */
if (!isset($attributes['__type'])) {
$attributes['__type'] = 'Object';
}
if (isset($this->map['__owner']) && !isset($attributes['__owner'])) {
$attributes['__owner'] = $this->getContactOwner();
}
if (!isset($attributes['__uid'])) {
$attributes['__uid'] = $this->_makeUid();
}
$key = $attributes['__key'] = $this->_makeKey($this->toDriverKeys($attributes));
$uid = $attributes['__uid'];
$attributes = $this->toDriverKeys($attributes);
$this->_add($attributes, $this->toDriverKeys($this->getBlobs()));
/* Log the creation of this item in the history log. */
try {
$GLOBALS['injector']->getInstance('Horde_History')
->log('turba:' . $this->getName() . ':' . $uid,
array('action' => 'add'), true);
} catch (Exception $e) {
Horde::logMessage($e, 'ERR');
}
return $key;
}
/**
* Returns ability of the backend to add new contacts.
*
* @return boolean Can backend add?
*/
public function canAdd()
{
return $this->_canAdd();
}
/**
* Returns ability of the backend to add new contacts.
*
* @return boolean Can backend add?
*/
protected function _canAdd()
{
return false;
}
/**
* Deletes the specified entry from the contact source.
*
* @param string $object_id The ID of the object to delete.
*
* @throws Turba_Exception
* @throws Horde_Exception_NotFound
*/
public function delete($object_id)
{
$object = $this->getObject($object_id);
if (!$object->hasPermission(Horde_Perms::DELETE)) {
throw new Turba_Exception(_("Permission denied"));
}
$this->_delete($this->toDriver('__key'), $object_id);
$own_contact = $GLOBALS['prefs']->getValue('own_contact');
if (!empty($own_contact)) {
@list(,$id) = explode(';', $own_contact);
if ($id == $object_id) {
$GLOBALS['prefs']->setValue('own_contact', '');
}
}
/* Log the deletion of this item in the history log. */
if ($object->getValue('__uid')) {
try {
$GLOBALS['injector']->getInstance('Horde_History')->log($object->getGuid(),
array('action' => 'delete'),
true);
} catch (Exception $e) {
Horde::logMessage($e, 'ERR');
}
}
/* Remove any CalDAV mappings. */
try {
$GLOBALS['injector']
->getInstance('Horde_Dav_Storage')
->deleteInternalObjectId($object_id, $this->_name);
} catch (Horde_Exception $e) {
Horde::log($e);
}
}
/**
* Deletes all contacts from an address book.
*
* @param string $sourceName The identifier of the address book to
* delete. If omitted, will clear the current
* user's 'default' address book for this
* source type.
*
* @throws Turba_Exception
*/
public function deleteAll($sourceName = null)
{
if (!$this->hasCapability('delete_all')) {
throw new Turba_Exception('Not supported');
}
$ids = $this->_deleteAll($sourceName);
// Update Horde_History
$history = $GLOBALS['injector']->getInstance('Horde_History');
try {
foreach ($ids as $id) {
// This is slightly hackish, but it saves us from having to
// create and save an array of Turba_Objects before we delete
// them, just to be able to calculate this using
// Turba_Object#getGuid
$guid = 'turba:' . $this->getName() . ':' . $id;
$history->log($guid, array('action' => 'delete'), true);
}
} catch (Exception $e) {
Horde::logMessage($e, 'ERR');
}
}
/**
* Deletes all contacts from an address book.
*
* @param string $sourceName The source to remove all contacts from.
*
* @return array An array of UIDs that have been deleted.
* @throws Turba_Exception
*/
protected function _deleteAll()
{
return array();
}
/**
* Modifies an existing entry in the contact source.
*
* @param Turba_Object $object The object to update.
*
* @return string The object id, possibly updated.
* @throws Turba_Exception
*/
public function save(Turba_Object $object)
{
$object_id = $this->_save($object);
/* Log the modification of this item in the history log. */
if ($object->getValue('__uid')) {
try {
$GLOBALS['injector']->getInstance('Horde_History')->log($object->getGuid(),
array('action' => 'modify'),
true);
} catch (Exception $e) {
Horde::logMessage($e, 'ERR');
}
}
return $object_id;
}
/**
* Returns the criteria available for this source except '__key'.
*
* @return array An array containing the criteria.
*/
public function getCriteria()
{
$criteria = $this->map;
unset($criteria['__key']);
return $criteria;
}
/**
* Returns all non-composite fields for this source. Useful for importing
* and exporting data, etc.
*
* @return array The field list.
*/
public function getFields()
{
return array_flip($this->fields);
}
/**
* Exports a given Turba_Object as an iCalendar vCard.
*
* @param Turba_Object $object Turba_Object.
* @param string $version The vcard version to produce.
* @param array $fields Hash of field names and
* Horde_SyncMl_Property properties with the
* requested fields.
* @param boolean $skipEmpty Whether to skip empty fields.
*
* @return Horde_Icalendar_Vcard A vcard object.
*/
public function tovCard(Turba_Object $object, $version = '2.1',
array $fields = null, $skipEmpty = false)
{
$hash = $object->getAttributes();
$vcard = new Horde_Icalendar_Vcard($version);
$formattedname = false;
$charset = ($version == '2.1')
? array('CHARSET' => 'UTF-8')
: array();
$haveDecodeHook = Horde::hookExists('decode_attribute', 'turba');
foreach ($hash as $key => $val) {
if ($skipEmpty && !strlen($val)) {
continue;
}
if ($haveDecodeHook) {
try {
$val = Horde::callHook(
'decode_attribute',
array($key, $val, $object),
'turba');
} catch (Turba_Exception $e) {}
}
switch ($key) {
case '__uid':
$vcard->setAttribute('UID', $val);
break;
case 'name':
if ($fields && !isset($fields['FN'])) {
break;
}
$vcard->setAttribute('FN', $val, Horde_Mime::is8bit($val) ? $charset : array());
$formattedname = true;
break;
case 'nickname':
case 'alias':
$params = Horde_Mime::is8bit($val) ? $charset : array();
if (!$fields || isset($fields['NICKNAME'])) {
$vcard->setAttribute('NICKNAME', $val, $params);
}
if (!$fields || isset($fields['X-EPOCSECONDNAME'])) {
$vcard->setAttribute('X-EPOCSECONDNAME', $val, $params);
}
break;
case 'homeAddress':
if ($fields &&
(!isset($fields['LABEL']) ||
(isset($fields['LABEL']->Params['TYPE']) &&
!$this->_hasValEnum($fields['LABEL']->Params['TYPE']->ValEnum, 'HOME')))) {
break;
}
if ($version == '2.1') {
$vcard->setAttribute('LABEL', $val, array('HOME' => null));
} else {
$vcard->setAttribute('LABEL', $val, array('TYPE' => 'HOME'));
}
break;
case 'workAddress':
if ($fields &&
(!isset($fields['LABEL']) ||
(isset($fields['LABEL']->Params['TYPE']) &&
!$this->_hasValEnum($fields['LABEL']->Params['TYPE']->ValEnum, 'WORK')))) {
break;
}
if ($version == '2.1') {
$vcard->setAttribute('LABEL', $val, array('WORK' => null));
} else {
$vcard->setAttribute('LABEL', $val, array('TYPE' => 'WORK'));
}
break;
case 'otherAddress':
if ($fields && !isset($fields['LABEL'])) {
break;
}
$vcard->setAttribute('LABEL', $val);
break;
case 'phone':
if ($fields && !isset($fields['TEL'])) {
break;
}
$vcard->setAttribute('TEL', $val);
break;
case 'homePhone':
if ($fields &&
(!isset($fields['TEL']) ||
(isset($fields['TEL']->Params['TYPE']) &&
!$this->_hasValEnum($fields['TEL']->Params['TYPE']->ValEnum, 'HOME')))) {
break;
}
if ($version == '2.1') {
$vcard->setAttribute('TEL', $val, array('HOME' => null, 'VOICE' => null));
} else {
$vcard->setAttribute('TEL', $val, array('TYPE' => array('HOME', 'VOICE')));
}
break;
case 'workPhone':
if ($fields &&
(!isset($fields['TEL']) ||
(isset($fields['TEL']->Params['TYPE']) &&
!$this->_hasValEnum($fields['TEL']->Params['TYPE']->ValEnum, 'WORK')))) {
break;
}
if ($version == '2.1') {
$vcard->setAttribute('TEL', $val, array('WORK' => null, 'VOICE' => null));
} else {
$vcard->setAttribute('TEL', $val, array('TYPE' => array('WORK', 'VOICE')));
}
break;
case 'cellPhone':
if ($fields &&
(!isset($fields['TEL']) ||
(isset($fields['TEL']->Params['TYPE']) &&
!$this->_hasValEnum($fields['TEL']->Params['TYPE']->ValEnum, 'CELL')))) {
break;
}
if ($version == '2.1') {
$vcard->setAttribute('TEL', $val, array('CELL' => null, 'VOICE' => null));
} else {
$vcard->setAttribute('TEL', $val, array('TYPE' => array('CELL', 'VOICE')));
}
break;
case 'homeCellPhone':
$parameters = array();
if ($fields) {
if (!isset($fields['TEL'])) {
break;
}
if (!isset($fields['TEL']->Params['TYPE']) ||
$this->_hasValEnum($fields['TEL']->Params['TYPE']->ValEnum, 'CELL')) {
if ($version == '2.1') {
$parameters['CELL'] = null;
$parameters['VOICE'] = null;
} else {
$parameters['TYPE'] = array('CELL', 'VOICE');
}
}
if (!isset($fields['TEL']->Params['TYPE']) ||
$this->_hasValEnum($fields['TEL']->Params['TYPE']->ValEnum, 'HOME')) {
if ($version == '2.1') {
$parameters['HOME'] = null;
$parameters['VOICE'] = null;
} else {
$parameters['TYPE'] = array('HOME', 'VOICE');
}
}
if (empty($parameters)) {
break;
}
} else {
if ($version == '2.1') {
$parameters = array('CELL' => null, 'HOME' => null, 'VOICE' => null);
} else {
$parameters = array('TYPE' => array('CELL', 'HOME', 'VOICE'));
}
}
$vcard->setAttribute('TEL', $val, $parameters);
break;
case 'workCellPhone':
$parameters = array();
if ($fields) {
if (!isset($fields['TEL'])) {
break;
}
if (!isset($fields['TEL']->Params['TYPE']) ||
$this->_hasValEnum($fields['TEL']->Params['TYPE']->ValEnum, 'CELL')) {
if ($version == '2.1') {
$parameters['CELL'] = null;
$parameters['VOICE'] = null;
} else {
$parameters['TYPE'] = array('CELL', 'VOICE');
}
}
if (!isset($fields['TEL']->Params['TYPE']) ||
$this->_hasValEnum($fields['TEL']->Params['TYPE']->ValEnum, 'WORK')) {
if ($version == '2.1') {
$parameters['WORK'] = null;
$parameters['VOICE'] = null;
} else {
$parameters['TYPE'] = array('WORK', 'VOICE');
}
}
if (empty($parameters)) {
break;
}
} else {
if ($version == '2.1') {
$parameters = array('CELL' => null, 'WORK' => null, 'VOICE' => null);
} else {
$parameters = array('TYPE' => array('CELL', 'WORK', 'VOICE'));
}
}
$vcard->setAttribute('TEL', $val, $parameters);
break;
case 'videoCall':
if ($fields &&
(!isset($fields['TEL']) ||
(isset($fields['TEL']->Params['TYPE']) &&
!$this->_hasValEnum($fields['TEL']->Params['TYPE']->ValEnum, 'VIDEO')))) {
break;
}
if ($version == '2.1') {
$vcard->setAttribute('TEL', $val, array('VIDEO' => null));
} else {
$vcard->setAttribute('TEL', $val, array('TYPE' => 'VIDEO'));
}
break;
case 'homeVideoCall':
$parameters = array();
if ($fields) {
if (!isset($fields['TEL'])) {
break;
}
if (!isset($fields['TEL']->Params['TYPE']) ||
$this->_hasValEnum($fields['TEL']->Params['TYPE']->ValEnum, 'VIDEO')) {
if ($version == '2.1') {
$parameters['VIDEO'] = null;
} else {
$parameters['TYPE'] = 'VIDEO';
}
}
if (!isset($fields['TEL']->Params['TYPE']) ||
$this->_hasValEnum($fields['TEL']->Params['TYPE']->ValEnum, 'HOME')) {
if ($version == '2.1') {
$parameters['HOME'] = null;
} else {
$parameters['TYPE'] = 'HOME';
}
}
if (empty($parameters)) {
break;
}
} else {
if ($version == '2.1') {
$parameters = array('VIDEO' => null, 'HOME' => null);
} else {
$parameters = array('TYPE' => array('VIDEO', 'HOME'));
}
}
$vcard->setAttribute('TEL', $val, $parameters);
break;
case 'workVideoCall':
$parameters = array();
if ($fields) {
if (!isset($fields['TEL'])) {
break;
}
if (!isset($fields['TEL']->Params['TYPE']) ||
$this->_hasValEnum($fields['TEL']->Params['TYPE']->ValEnum, 'VIDEO')) {
if ($version == '2.1') {
$parameters['VIDEO'] = null;
} else {
$parameters['TYPE'] = 'VIDEO';
}
}
if (!isset($fields['TEL']->Params['TYPE']) ||
$this->_hasValEnum($fields['TEL']->Params['TYPE']->ValEnum, 'WORK')) {
if ($version == '2.1') {
$parameters['WORK'] = null;
} else {
$parameters['TYPE'] = 'WORK';
}
}
if (empty($parameters)) {
break;
}
} else {
if ($version == '2.1') {
$parameters = array('VIDEO' => null, 'WORK' => null);
} else {
$parameters = array('TYPE' => array('VIDEO', 'WORK'));
}
}
$vcard->setAttribute('TEL', $val, $parameters);
break;
case 'sip':
if ($fields && !isset($fields['X-SIP'])) {
break;
}
$vcard->setAttribute('X-SIP', $val);
break;
case 'ptt':
if ($fields &&
(!isset($fields['X-SIP']) ||
(isset($fields['X-SIP']->Params['TYPE']) &&
!$this->_hasValEnum($fields['X-SIP']->Params['TYPE']->ValEnum, 'POC')))) {
break;
}
if ($version == '2.1') {
$vcard->setAttribute('X-SIP', $val, array('POC' => null));
} else {
$vcard->setAttribute('X-SIP', $val, array('TYPE' => 'POC'));
}
break;
case 'voip':
if ($fields &&
(!isset($fields['X-SIP']) ||
(isset($fields['X-SIP']->Params['TYPE']) &&
!$this->_hasValEnum($fields['X-SIP']->Params['TYPE']->ValEnum, 'VOIP')))) {
break;
}
if ($version == '2.1') {
$vcard->setAttribute('X-SIP', $val, array('VOIP' => null));
} else {
$vcard->setAttribute('X-SIP', $val, array('TYPE' => 'VOIP'));
}
break;
case 'shareView':
if ($fields &&
(!isset($fields['X-SIP']) ||
(isset($fields['X-SIP']->Params['TYPE']) &&
!$this->_hasValEnum($fields['X-SIP']->Params['TYPE']->ValEnum, 'SWIS')))) {
break;
}
if ($version == '2.1') {
$vcard->setAttribute('X-SIP', $val, array('SWIS' => null));
} else {
$vcard->setAttribute('X-SIP', $val, array('TYPE' => 'SWIS'));
}
break;
case 'imaddress':
if ($fields && !isset($fields['X-WV-ID'])) {
break;
}
$vcard->setAttribute('X-WV-ID', $val);
break;
case 'fax':
if ($fields &&
(!isset($fields['TEL']) ||
(isset($fields['TEL']->Params['TYPE']) &&
!$this->_hasValEnum($fields['TEL']->Params['TYPE']->ValEnum, 'FAX')))) {
break;
}
if ($version == '2.1') {
$vcard->setAttribute('TEL', $val, array('FAX' => null));
} else {
$vcard->setAttribute('TEL', $val, array('TYPE' => 'FAX'));
}
break;
case 'homeFax':
$parameters = array();
if ($fields) {
if (!isset($fields['TEL'])) {
break;
}
if (!isset($fields['TEL']->Params['TYPE']) ||
$this->_hasValEnum($fields['TEL']->Params['TYPE']->ValEnum, 'FAX')) {
if ($version == '2.1') {
$parameters['FAX'] = null;
} else {
$parameters['TYPE'] = 'FAX';
}
}
if (!isset($fields['TEL']->Params['TYPE']) ||
$this->_hasValEnum($fields['TEL']->Params['TYPE']->ValEnum, 'HOME')) {
if ($version == '2.1') {
$parameters['HOME'] = null;
} else {
$parameters['TYPE'] = 'HOME';
}
}
if (empty($parameters)) {
break;
}
} else {
if ($version == '2.1') {
$parameters = array('FAX' => null, 'HOME' => null);
} else {
$parameters = array('TYPE' => array('FAX', 'HOME'));
}
}
$vcard->setAttribute('TEL', $val, $parameters);
break;
case 'workFax':
$parameters = array();
if ($fields) {
if (!isset($fields['TEL'])) {
break;
}
if (!isset($fields['TEL']->Params['TYPE']) ||
$this->_hasValEnum($fields['TEL']->Params['TYPE']->ValEnum, 'FAX')) {
if ($version == '2.1') {
$parameters['FAX'] = null;
} else {
$parameters['TYPE'] = 'FAX';
}
}
if (!isset($fields['TEL']->Params['TYPE']) ||
$this->_hasValEnum($fields['TEL']->Params['TYPE']->ValEnum, 'WORK')) {
if ($version == '2.1') {
$parameters['WORK'] = null;
} else {
$parameters['TYPE'] = 'WORK';
}
}
if (empty($parameters)) {
break;
}
} else {
if ($version == '2.1') {
$parameters = array('FAX' => null, 'WORK' => null);
} else {
$parameters = array('TYPE' => array('FAX', 'WORK'));
}
}
$vcard->setAttribute('TEL', $val, $parameters);
if ($version == '2.1') {
$vcard->setAttribute('TEL', $val, array('FAX' => null, 'WORK' => null));
} else {
$vcard->setAttribute('TEL', $val, array('TYPE' => array('FAX', 'WORK')));
}
break;
case 'pager':
if ($fields &&
(!isset($fields['TEL']) ||
(isset($fields['TEL']->Params['TYPE']) &&
!$this->_hasValEnum($fields['TEL']->Params['TYPE']->ValEnum, 'PAGER')))) {
break;
}
if ($version == '2.1') {
$vcard->setAttribute('TEL', $val, array('PAGER' => null));
} else {
$vcard->setAttribute('TEL', $val, array('TYPE' => 'PAGER'));
}
break;
case 'email':
if ($fields && !isset($fields['EMAIL'])) {
break;
}
if ($version == '2.1') {
$vcard->setAttribute(
'EMAIL',
Horde_Icalendar_Vcard::getBareEmail($val),
array('INTERNET' => null));
} else {
$vcard->setAttribute(
'EMAIL',
Horde_Icalendar_Vcard::getBareEmail($val),
array('TYPE' => 'INTERNET'));
}
break;
case 'homeEmail':
if ($fields &&
(!isset($fields['EMAIL']) ||
(isset($fields['EMAIL']->Params['TYPE']) &&
!$this->_hasValEnum($fields['EMAIL']->Params['TYPE']->ValEnum, 'HOME')))) {
break;
}
if ($version == '2.1') {
$vcard->setAttribute('EMAIL',
Horde_Icalendar_Vcard::getBareEmail($val),
array('HOME' => null));
} else {
$vcard->setAttribute('EMAIL',
Horde_Icalendar_Vcard::getBareEmail($val),
array('TYPE' => 'HOME'));
}
break;
case 'workEmail':
if ($fields &&
(!isset($fields['EMAIL']) ||
(isset($fields['EMAIL']->Params['TYPE']) &&
!$this->_hasValEnum($fields['EMAIL']->Params['TYPE']->ValEnum, 'WORK')))) {
break;
}
if ($version == '2.1') {
$vcard->setAttribute('EMAIL',
Horde_Icalendar_Vcard::getBareEmail($val),
array('WORK' => null));
} else {
$vcard->setAttribute('EMAIL',
Horde_Icalendar_Vcard::getBareEmail($val),
array('TYPE' => 'WORK'));
}
break;
case 'emails':
if ($fields && !isset($fields['EMAIL'])) {
break;
}
$emails = explode(',', $val);
foreach ($emails as $email) {
$vcard->setAttribute('EMAIL', Horde_Icalendar_Vcard::getBareEmail($email));
}
break;
case 'title':
if ($fields && !isset($fields['TITLE'])) {
break;
}
$vcard->setAttribute('TITLE', $val, Horde_Mime::is8bit($val) ? $charset : array());
break;
case 'role':
if ($fields && !isset($fields['ROLE'])) {
break;
}
$vcard->setAttribute('ROLE', $val, Horde_Mime::is8bit($val) ? $charset : array());
break;
case 'notes':
if ($fields && !isset($fields['NOTE'])) {
break;
}
$vcard->setAttribute('NOTE', $val, Horde_Mime::is8bit($val) ? $charset : array());
break;
case 'businessCategory':
case 'category':
if ($fields && !isset($fields['CATEGORIES'])) {
break;
}
$vcard->setAttribute('CATEGORIES', $val);
break;
case 'anniversary':
if (!$fields || isset($fields['X-ANNIVERSARY'])) {
$vcard->setAttribute('X-ANNIVERSARY', $val);
}
break;
case 'spouse':
if (!$fields || isset($fields['X-SPOUSE'])) {
$vcard->setAttribute('X-SPOUSE', $val);
}
break;
case 'children':
if (!$fields || isset($fields['X-CHILDREN'])) {
$vcard->setAttribute('X-CHILDREN', $val);
}
break;
case 'website':
if ($fields && !isset($fields['URL'])) {
break;
}
$vcard->setAttribute('URL', $val);
break;
case 'homeWebsite':
if ($fields &&
(!isset($fields['URL']) ||
(isset($fields['URL']->Params['TYPE']) &&
!$this->_hasValEnum($fields['URL']->Params['TYPE']->ValEnum, 'HOME')))) {
break;
}
if ($version == '2.1') {
$vcard->setAttribute('URL', $val, array('HOME' => null));
} else {
$vcard->setAttribute('URL', $val, array('TYPE' => 'HOME'));
}
break;
case 'workWebsite':
if ($fields &&
(!isset($fields['URL']) ||
(isset($fields['URL']->Params['TYPE']) &&
!$this->_hasValEnum($fields['URL']->Params['TYPE']->ValEnum, 'WORK')))) {
break;
}
if ($version == '2.1') {
$vcard->setAttribute('URL', $val, array('WORK' => null));
} else {
$vcard->setAttribute('URL', $val, array('TYPE' => 'WORK'));
}
break;
case 'birthday':
if ($fields && !isset($fields['BDAY'])) {
break;
}
$vcard->setAttribute('BDAY', $val);
break;
case 'timezone':
if ($fields && !isset($fields['TZ'])) {
break;
}
$vcard->setAttribute('TZ', $val, array('VALUE' => 'text'));
break;
case 'latitude':
if ($fields && !isset($fields['GEO'])) {
break;
}
if (isset($hash['longitude'])) {
$vcard->setAttribute('GEO',
array('latitude' => $val,
'longitude' => $hash['longitude']));
}
break;
case 'homeLatitude':
if ($fields &&
(!isset($fields['GEO']) ||
(isset($fields['GEO']->Params['TYPE']) &&
!$this->_hasValEnum($fields['GEO']->Params['TYPE']->ValEnum, 'HOME')))) {
break;
}
if (isset($hash['homeLongitude'])) {
if ($version == '2.1') {
$vcard->setAttribute('GEO',
array('latitude' => $val,
'longitude' => $hash['homeLongitude']),
array('HOME' => null));
} else {
$vcard->setAttribute('GEO',
array('latitude' => $val,
'longitude' => $hash['homeLongitude']),
array('TYPE' => 'HOME'));
}
}
break;
case 'workLatitude':
if ($fields &&
(!isset($fields['GEO']) ||
(isset($fields['GEO']->Params['TYPE']) &&
!$this->_hasValEnum($fields['GEO']->Params['TYPE']->ValEnum, 'HOME')))) {
break;
}
if (isset($hash['workLongitude'])) {
if ($version == '2.1') {
$vcard->setAttribute('GEO',
array('latitude' => $val,
'longitude' => $hash['workLongitude']),
array('WORK' => null));
} else {
$vcard->setAttribute('GEO',
array('latitude' => $val,
'longitude' => $hash['workLongitude']),
array('TYPE' => 'WORK'));
}
}
break;
case 'photo':
case 'logo':
$name = Horde_String::upper($key);
$params = array();
if (strlen($val)) {
$params['ENCODING'] = 'b';
}
if (isset($hash[$key . 'type'])) {
$params['TYPE'] = $hash[$key . 'type'];
}
if ($fields &&
(!isset($fields[$name]) ||
(isset($params['TYPE']) &&
isset($fields[$name]->Params['TYPE']) &&
!$this->_hasValEnum($fields[$name]->Params['TYPE']->ValEnum, $params['TYPE'])))) {
break;
}
$vcard->setAttribute($name,
base64_encode($val),
$params);
break;
}
}
// No explicit firstname/lastname in data source: we have to guess.
if (!isset($hash['lastname']) && isset($hash['name'])) {
$this->_guessName($hash);
}
$a = array(
Horde_Icalendar_Vcard::N_FAMILY => isset($hash['lastname']) ? $hash['lastname'] : '',
Horde_Icalendar_Vcard::N_GIVEN => isset($hash['firstname']) ? $hash['firstname'] : '',
Horde_Icalendar_Vcard::N_ADDL => isset($hash['middlenames']) ? $hash['middlenames'] : '',
Horde_Icalendar_Vcard::N_PREFIX => isset($hash['namePrefix']) ? $hash['namePrefix'] : '',
Horde_Icalendar_Vcard::N_SUFFIX => isset($hash['nameSuffix']) ? $hash['nameSuffix'] : '',
);
$val = implode(';', $a);
if (!$fields || isset($fields['N'])) {
$vcard->setAttribute('N', $val, Horde_Mime::is8bit($val) ? $charset : array(), false, $a);
}
if (!$formattedname && (!$fields || isset($fields['FN']))) {
if ($object->getValue('name')) {
$val = $object->getValue('name');
} elseif (!empty($this->alternativeName) &&
isset($hash[$this->alternativeName])) {
$val = $hash[$this->alternativeName];
} else {
$val = '';
}
$vcard->setAttribute('FN', $val, Horde_Mime::is8bit($val) ? $charset : array());
}
$org = array();
if (!empty($hash['company']) ||
(!$skipEmpty && array_key_exists('company', $hash))) {
$org[] = $hash['company'];
}
if (!empty($hash['department']) ||
(!$skipEmpty && array_key_exists('department', $hash))) {
$org[] = $hash['department'];
}
if (count($org) && (!$fields || isset($fields['ORG']))) {
$val = implode(';', $org);
$vcard->setAttribute('ORG', $val, Horde_Mime::is8bit($val) ? $charset : array(), false, $org);
}
if ((!$fields || isset($fields['ADR'])) &&
(!empty($hash['commonAddress']) ||
!empty($hash['commonStreet']) ||
!empty($hash['commonPOBox']) ||
!empty($hash['commonExtended']) ||
!empty($hash['commonCity']) ||
!empty($hash['commonProvince']) ||
!empty($hash['commonPostalCode']) ||
!empty($hash['commonCountry']) ||
(!$skipEmpty &&
(array_key_exists('commonAddress', $hash) ||
array_key_exists('commonStreet', $hash) ||
array_key_exists('commonPOBox', $hash) ||
array_key_exists('commonExtended', $hash) ||
array_key_exists('commonCity', $hash) ||
array_key_exists('commonProvince', $hash) ||
array_key_exists('commonPostalCode', $hash) ||
array_key_exists('commonCountry', $hash))))) {
/* We can't know if this particular Turba source uses a single
* address field or multiple for
* street/city/province/postcode/country. Try to deal with
* both. */
if (isset($hash['commonAddress']) &&
!isset($hash['commonStreet'])) {
$hash['commonStreet'] = $hash['commonAddress'];
}
$a = array(
Horde_Icalendar_Vcard::ADR_POB => isset($hash['commonPOBox'])
? $hash['commonPOBox'] : '',
Horde_Icalendar_Vcard::ADR_EXTEND => isset($hash['commonExtended'])
? $hash['commonExtended'] : '',
Horde_Icalendar_Vcard::ADR_STREET => isset($hash['commonStreet'])
? $hash['commonStreet'] : '',
Horde_Icalendar_Vcard::ADR_LOCALITY => isset($hash['commonCity'])
? $hash['commonCity'] : '',
Horde_Icalendar_Vcard::ADR_REGION => isset($hash['commonProvince'])
? $hash['commonProvince'] : '',
Horde_Icalendar_Vcard::ADR_POSTCODE => isset($hash['commonPostalCode'])
? $hash['commonPostalCode'] : '',
Horde_Icalendar_Vcard::ADR_COUNTRY => isset($hash['commonCountry'])
? Horde_Nls::getCountryISO($hash['commonCountry']) : '',
);
$val = implode(';', $a);
if ($version == '2.1') {
$params = array();
if (Horde_Mime::is8bit($val)) {
$params['CHARSET'] = 'UTF-8';
}
} else {
$params = array('TYPE' => '');
}
$vcard->setAttribute('ADR', $val, $params, true, $a);
}
if ((!$fields ||
(isset($fields['ADR']) &&
(!isset($fields['ADR']->Params['TYPE']) ||
$this->_hasValEnum($fields['ADR']->Params['TYPE']->ValEnum, 'HOME')))) &&
(!empty($hash['homeAddress']) ||
!empty($hash['homeStreet']) ||
!empty($hash['homePOBox']) ||
!empty($hash['homeExtended']) ||
!empty($hash['homeCity']) ||
!empty($hash['homeProvince']) ||
!empty($hash['homePostalCode']) ||
!empty($hash['homeCountry']) ||
(!$skipEmpty &&
(array_key_exists('homeAddress', $hash) ||
array_key_exists('homeStreet', $hash) ||
array_key_exists('homePOBox', $hash) ||
array_key_exists('homeExtended', $hash) ||
array_key_exists('homeCity', $hash) ||
array_key_exists('homeProvince', $hash) ||
array_key_exists('homePostalCode', $hash) ||
array_key_exists('homeCountry', $hash))))) {
if (isset($hash['homeAddress']) && !isset($hash['homeStreet'])) {
$hash['homeStreet'] = $hash['homeAddress'];
}
$a = array(
Horde_Icalendar_Vcard::ADR_POB => isset($hash['homePOBox'])
? $hash['homePOBox'] : '',
Horde_Icalendar_Vcard::ADR_EXTEND => isset($hash['homeExtended'])
? $hash['homeExtended'] : '',
Horde_Icalendar_Vcard::ADR_STREET => isset($hash['homeStreet'])
? $hash['homeStreet'] : '',
Horde_Icalendar_Vcard::ADR_LOCALITY => isset($hash['homeCity'])
? $hash['homeCity'] : '',
Horde_Icalendar_Vcard::ADR_REGION => isset($hash['homeProvince'])
? $hash['homeProvince'] : '',
Horde_Icalendar_Vcard::ADR_POSTCODE => isset($hash['homePostalCode'])
? $hash['homePostalCode'] : '',
Horde_Icalendar_Vcard::ADR_COUNTRY => isset($hash['homeCountry'])
? Horde_Nls::getCountryISO($hash['homeCountry']) : '',
);
$val = implode(';', $a);
if ($version == '2.1') {
$params = array('HOME' => null);
if (Horde_Mime::is8bit($val)) {
$params['CHARSET'] = 'UTF-8';
}
} else {
$params = array('TYPE' => 'HOME');
}
$vcard->setAttribute('ADR', $val, $params, true, $a);
}
if ((!$fields ||
(isset($fields['ADR']) &&
(!isset($fields['ADR']->Params['TYPE']) ||
$this->_hasValEnum($fields['ADR']->Params['TYPE']->ValEnum, 'WORK')))) &&
(!empty($hash['workAddress']) ||
!empty($hash['workStreet']) ||
!empty($hash['workPOBox']) ||
!empty($hash['workExtended']) ||
!empty($hash['workCity']) ||
!empty($hash['workProvince']) ||
!empty($hash['workPostalCode']) ||
!empty($hash['workCountry']) ||
(!$skipEmpty &&
(array_key_exists('workAddress', $hash) ||
array_key_exists('workStreet', $hash) ||
array_key_exists('workPOBox', $hash) ||
array_key_exists('workExtended', $hash) ||
array_key_exists('workCity', $hash) ||
array_key_exists('workProvince', $hash) ||
array_key_exists('workPostalCode', $hash) ||
array_key_exists('workCountry', $hash))))) {
if (isset($hash['workAddress']) && !isset($hash['workStreet'])) {
$hash['workStreet'] = $hash['workAddress'];
}
$a = array(
Horde_Icalendar_Vcard::ADR_POB => isset($hash['workPOBox'])
? $hash['workPOBox'] : '',
Horde_Icalendar_Vcard::ADR_EXTEND => isset($hash['workExtended'])
? $hash['workExtended'] : '',
Horde_Icalendar_Vcard::ADR_STREET => isset($hash['workStreet'])
? $hash['workStreet'] : '',
Horde_Icalendar_Vcard::ADR_LOCALITY => isset($hash['workCity'])
? $hash['workCity'] : '',
Horde_Icalendar_Vcard::ADR_REGION => isset($hash['workProvince'])
? $hash['workProvince'] : '',
Horde_Icalendar_Vcard::ADR_POSTCODE => isset($hash['workPostalCode'])
? $hash['workPostalCode'] : '',
Horde_Icalendar_Vcard::ADR_COUNTRY => isset($hash['workCountry'])
? Horde_Nls::getCountryISO($hash['workCountry']) : '',
);
$val = implode(';', $a);
if ($version == '2.1') {
$params = array('WORK' => null);
if (Horde_Mime::is8bit($val)) {
$params['CHARSET'] = 'UTF-8';
}
} else {
$params = array('TYPE' => 'WORK');
}
$vcard->setAttribute('ADR', $val, $params, true, $a);
}
return $vcard;
}
/**
* Returns whether a ValEnum entry from a DevInf object contains a certain
* type.
*
* @param array $valEnum A ValEnum hash.
* @param string $type A requested attribute type.
*
* @return boolean True if $type exists in $valEnum.
*/
protected function _hasValEnum($valEnum, $type)
{
foreach (array_keys($valEnum) as $key) {
if (in_array($type, explode(',', $key))) {
return true;
}
}
return false;
}
/**
* Function to convert a Horde_Icalendar_Vcard object into a Turba
* Object Hash with Turba attributes suitable as a parameter for add().
*
* @see add()
*
* @param Horde_Icalendar_Vcard $vcard The Horde_Icalendar_Vcard object
* to parse.
*
* @return array A Turba attribute hash.
*/
public function toHash(Horde_Icalendar_Vcard $vcard)
{
$hash = array();
$attr = $vcard->getAllAttributes();
foreach ($attr as $item) {
switch ($item['name']) {
case 'UID':
$hash['__uid'] = $item['value'];
break;
case 'FN':
$hash['name'] = $item['value'];
break;
case 'N':
$name = $item['values'];
if (!empty($name[Horde_Icalendar_Vcard::N_FAMILY])) {
$hash['lastname'] = $name[Horde_Icalendar_Vcard::N_FAMILY];
}
if (!empty($name[Horde_Icalendar_Vcard::N_GIVEN])) {
$hash['firstname'] = $name[Horde_Icalendar_Vcard::N_GIVEN];
}
if (!empty($name[Horde_Icalendar_Vcard::N_ADDL])) {
$hash['middlenames'] = $name[Horde_Icalendar_Vcard::N_ADDL];
}
if (!empty($name[Horde_Icalendar_Vcard::N_PREFIX])) {
$hash['namePrefix'] = $name[Horde_Icalendar_Vcard::N_PREFIX];
}
if (!empty($name[Horde_Icalendar_Vcard::N_SUFFIX])) {
$hash['nameSuffix'] = $name[Horde_Icalendar_Vcard::N_SUFFIX];
}
break;
case 'NICKNAME':
case 'X-EPOCSECONDNAME':
$hash['nickname'] = $item['value'];
$hash['alias'] = $item['value'];
break;
// We use LABEL but also support ADR.
case 'LABEL':
if (isset($item['params']['HOME']) && !isset($hash['homeAddress'])) {
$hash['homeAddress'] = $item['value'];
} elseif (isset($item['params']['WORK']) && !isset($hash['workAddress'])) {
$hash['workAddress'] = $item['value'];
} elseif (!isset($hash['commonAddress'])) {
$hash['commonAddress'] = $item['value'];
}
break;
case 'ADR':
if (isset($item['params']['TYPE'])) {
if (!is_array($item['params']['TYPE'])) {
$item['params']['TYPE'] = array($item['params']['TYPE']);
}
} else {
$item['params']['TYPE'] = array();
if (isset($item['params']['WORK'])) {
$item['params']['TYPE'][] = 'WORK';
}
if (isset($item['params']['HOME'])) {
$item['params']['TYPE'][] = 'HOME';
}
if (count($item['params']['TYPE']) == 0) {
$item['params']['TYPE'][] = 'COMMON';
}
}
$address = $item['values'];
foreach ($item['params']['TYPE'] as $adr) {
switch (Horde_String::upper($adr)) {
case 'HOME':
$prefix = 'home';
break;
case 'WORK':
$prefix = 'work';
break;
default:
$prefix = 'common';
}
if (isset($hash[$prefix . 'Address'])) {
continue;
}
$hash[$prefix . 'Address'] = '';
if (!empty($address[Horde_Icalendar_Vcard::ADR_STREET])) {
$hash[$prefix . 'Street'] = $address[Horde_Icalendar_Vcard::ADR_STREET];
$hash[$prefix . 'Address'] .= $hash[$prefix . 'Street'] . "\n";
}
if (!empty($address[Horde_Icalendar_Vcard::ADR_EXTEND])) {
$hash[$prefix . 'Extended'] = $address[Horde_Icalendar_Vcard::ADR_EXTEND];
$hash[$prefix . 'Address'] .= $hash[$prefix . 'Extended'] . "\n";
}
if (!empty($address[Horde_Icalendar_Vcard::ADR_POB])) {
$hash[$prefix . 'POBox'] = $address[Horde_Icalendar_Vcard::ADR_POB];
$hash[$prefix . 'Address'] .= $hash[$prefix . 'POBox'] . "\n";
}
if (!empty($address[Horde_Icalendar_Vcard::ADR_LOCALITY])) {
$hash[$prefix . 'City'] = $address[Horde_Icalendar_Vcard::ADR_LOCALITY];
$hash[$prefix . 'Address'] .= $hash[$prefix . 'City'];
}
if (!empty($address[Horde_Icalendar_Vcard::ADR_REGION])) {
$hash[$prefix . 'Province'] = $address[Horde_Icalendar_Vcard::ADR_REGION];
$hash[$prefix . 'Address'] .= ', ' . $hash[$prefix . 'Province'];
}
if (!empty($address[Horde_Icalendar_Vcard::ADR_POSTCODE])) {
$hash[$prefix . 'PostalCode'] = $address[Horde_Icalendar_Vcard::ADR_POSTCODE];
$hash[$prefix . 'Address'] .= ' ' . $hash[$prefix . 'PostalCode'];
}
if (!empty($address[Horde_Icalendar_Vcard::ADR_COUNTRY])) {
include 'Horde/Nls/Countries.php';
$country = array_search($address[Horde_Icalendar_Vcard::ADR_COUNTRY], $countries);
if ($country === false) {
$country = $address[Horde_Icalendar_Vcard::ADR_COUNTRY];
}
$hash[$prefix . 'Country'] = $country;
$hash[$prefix . 'Address'] .= "\n" . $address[Horde_Icalendar_Vcard::ADR_COUNTRY];
}
$hash[$prefix . 'Address'] = trim($hash[$prefix . 'Address']);
}
break;
case 'TZ':
// We only support textual timezones.
if (!isset($item['params']['VALUE']) ||
Horde_String::lower($item['params']['VALUE']) != 'text') {
break;
}
$timezones = explode(';', $item['value']);
$available_timezones = Horde_Nls::getTimezones();
foreach ($timezones as $timezone) {
$timezone = trim($timezone);
if (isset($available_timezones[$timezone])) {
$hash['timezone'] = $timezone;
break 2;
}
}
break;
case 'GEO':
if (isset($item['params']['HOME'])) {
$hash['homeLatitude'] = $item['value']['latitude'];
$hash['homeLongitude'] = $item['value']['longitude'];
} elseif (isset($item['params']['WORK'])) {
$hash['workLatitude'] = $item['value']['latitude'];
$hash['workLongitude'] = $item['value']['longitude'];
} else {
$hash['latitude'] = $item['value']['latitude'];
$hash['longitude'] = $item['value']['longitude'];
}
break;
case 'TEL':
if (isset($item['params']['FAX'])) {
if (isset($item['params']['WORK']) &&
!isset($hash['workFax'])) {
$hash['workFax'] = $item['value'];
} elseif (isset($item['params']['HOME']) &&
!isset($hash['homeFax'])) {
$hash['homeFax'] = $item['value'];
} elseif (!isset($hash['fax'])) {
$hash['fax'] = $item['value'];
}
} elseif (isset($item['params']['PAGER']) &&
!isset($hash['pager'])) {
$hash['pager'] = $item['value'];
} elseif (isset($item['params']['TYPE'])) {
if (!is_array($item['params']['TYPE'])) {
$item['params']['TYPE'] = array($item['params']['TYPE']);
}
// For vCard 3.0.
if (in_array('CELL', $item['params']['TYPE'])) {
if (in_array('HOME', $item['params']['TYPE']) &&
!isset($hash['homeCellPhone'])) {
$hash['homeCellPhone'] = $item['value'];
} elseif (in_array('WORK', $item['params']['TYPE']) &&
!isset($hash['workCellPhone'])) {
$hash['workCellPhone'] = $item['value'];
} elseif (!isset($hash['cellPhone'])) {
$hash['cellPhone'] = $item['value'];
}
} elseif (in_array('FAX', $item['params']['TYPE'])) {
if (in_array('HOME', $item['params']['TYPE']) &&
!isset($hash['homeFax'])) {
$hash['homeFax'] = $item['value'];
} elseif (in_array('WORK', $item['params']['TYPE']) &&
!isset($hash['workFax'])) {
$hash['workFax'] = $item['value'];
} elseif (!isset($hash['fax'])) {
$hash['fax'] = $item['value'];
}
} elseif (in_array('VIDEO', $item['params']['TYPE'])) {
if (in_array('HOME', $item['params']['TYPE']) &&
!isset($hash['homeVideoCall'])) {
$hash['homeVideoCall'] = $item['value'];
} elseif (in_array('WORK', $item['params']['TYPE']) &&
!isset($hash['workVideoCall'])) {
$hash['workVideoCall'] = $item['value'];
} elseif (!isset($hash['videoCall'])) {
$hash['videoCall'] = $item['value'];
}
} elseif (in_array('PAGER', $item['params']['TYPE']) &&
!isset($hash['pager'])) {
$hash['pager'] = $item['value'];
} elseif (in_array('WORK', $item['params']['TYPE']) &&
!isset($hash['workPhone'])) {
$hash['workPhone'] = $item['value'];
} elseif (in_array('HOME', $item['params']['TYPE']) &&
!isset($hash['homePhone'])) {
$hash['homePhone'] = $item['value'];
} elseif (!isset($hash['phone'])) {
$hash['phone'] = $item['value'];
}
} elseif (isset($item['params']['CELL'])) {
if (isset($item['params']['WORK']) &&
!isset($hash['workCellPhone'])) {
$hash['workCellPhone'] = $item['value'];
} elseif (isset($item['params']['HOME']) &&
!isset($hash['homeCellPhone'])) {
$hash['homeCellPhone'] = $item['value'];
} elseif (!isset($hash['cellPhone'])) {
$hash['cellPhone'] = $item['value'];
}
} elseif (isset($item['params']['VIDEO'])) {
if (isset($item['params']['WORK']) &&
!isset($hash['workVideoCall'])) {
$hash['workVideoCall'] = $item['value'];
} elseif (isset($item['params']['HOME']) &&
!isset($hash['homeVideoCall'])) {
$hash['homeVideoCall'] = $item['value'];
} elseif (!isset($hash['videoCall'])) {
$hash['videoCall'] = $item['value'];
}
} else {
if (isset($item['params']['WORK']) &&
!isset($hash['workPhone'])) {
$hash['workPhone'] = $item['value'];
} elseif (isset($item['params']['HOME']) &&
!isset($hash['homePhone'])) {
$hash['homePhone'] = $item['value'];
} else {
$hash['phone'] = $item['value'];
}
}
break;
case 'EMAIL':
$email_set = false;
if (isset($item['params']['HOME']) &&
(!isset($hash['homeEmail']) ||
isset($item['params']['PREF']))) {
$e = Horde_Icalendar_Vcard::getBareEmail($item['value']);
$hash['homeEmail'] = $e ? $e : '';
$email_set = true;
} elseif (isset($item['params']['WORK']) &&
(!isset($hash['workEmail']) ||
isset($item['params']['PREF']))) {
$e = Horde_Icalendar_Vcard::getBareEmail($item['value']);
$hash['workEmail'] = $e ? $e : '';
$email_set = true;
} elseif (isset($item['params']['TYPE'])) {
if (!is_array($item['params']['TYPE'])) {
$item['params']['TYPE'] = array($item['params']['TYPE']);
}
if (in_array('HOME', $item['params']['TYPE']) &&
(!isset($hash['homeEmail']) ||
in_array('PREF', $item['params']['TYPE']))) {
$e = Horde_Icalendar_Vcard::getBareEmail($item['value']);
$hash['homeEmail'] = $e ? $e : '';
$email_set = true;
} elseif (in_array('WORK', $item['params']['TYPE']) &&
(!isset($hash['workEmail']) ||
in_array('PREF', $item['params']['TYPE']))) {
$e = Horde_Icalendar_Vcard::getBareEmail($item['value']);
$hash['workEmail'] = $e ? $e : '';
$email_set = true;
}
}
if (!$email_set &&
(!isset($hash['email']) ||
isset($item['params']['PREF']))) {
$e = Horde_Icalendar_Vcard::getBareEmail($item['value']);
$hash['email'] = $e ? $e : '';
}
if (!isset($hash['emails'])) {
$e = Horde_Icalendar_Vcard::getBareEmail($item['value']);
$hash['emails'] = $e ? $e : '';
} else {
$hash['emails'] .= ',' . Horde_Icalendar_Vcard::getBareEmail($item['value']);
}
break;
case 'TITLE':
$hash['title'] = $item['value'];
break;
case 'ROLE':
$hash['role'] = $item['value'];
break;
case 'ORG':
// The VCARD 2.1 specification requires the presence of two
// SEMI-COLON separated fields: Organizational Name and
// Organizational Unit. Additional fields are optional.
$hash['company'] = !empty($item['values'][0]) ? $item['values'][0] : '';
$hash['department'] = !empty($item['values'][1]) ? $item['values'][1] : '';
break;
case 'NOTE':
$hash['notes'] = $item['value'];
break;
case 'CATEGORIES':
$hash['businessCategory'] = $hash['category'] = str_replace('\; ', ';', $item['value']);
break;
case 'URL':
if (isset($item['params']['HOME']) &&
!isset($hash['homeWebsite'])) {
$hash['homeWebsite'] = $item['value'];
} elseif (isset($item['params']['WORK']) &&
!isset($hash['workWebsite'])) {
$hash['workWebsite'] = $item['value'];
} elseif (!isset($hash['website'])) {
$hash['website'] = $item['value'];
}
break;
case 'BDAY':
if (empty($item['value'])) {
$hash['birthday'] = '';
} else {
$hash['birthday'] = $item['value']['year'] . '-' . $item['value']['month'] . '-' . $item['value']['mday'];
}
break;
case 'PHOTO':
case 'LOGO':
if (isset($item['params']['VALUE']) &&
Horde_String::lower($item['params']['VALUE']) == 'uri') {
// No support for URIs yet.
break;
}
if (!isset($item['params']['ENCODING']) ||
(Horde_String::lower($item['params']['ENCODING']) != 'b' &&
Horde_String::upper($item['params']['ENCODING']) != 'BASE64')) {
// Invalid property.
break;
}
$type = Horde_String::lower($item['name']);
$hash[$type] = base64_decode($item['value']);
if (isset($item['params']['TYPE'])) {
$hash[$type . 'type'] = $item['params']['TYPE'];
}
break;
case 'X-SIP':
if (isset($item['params']['POC']) &&
!isset($hash['ptt'])) {
$hash['ptt'] = $item['value'];
} elseif (isset($item['params']['VOIP']) &&
!isset($hash['voip'])) {
$hash['voip'] = $item['value'];
} elseif (isset($item['params']['SWIS']) &&
!isset($hash['shareView'])) {
$hash['shareView'] = $item['value'];
} elseif (!isset($hash['sip'])) {
$hash['sip'] = $item['value'];
}
break;
case 'X-WV-ID':
$hash['imaddress'] = $item['value'];
break;
case 'X-ANNIVERSARY':
if (empty($item['value'])) {
$hash['anniversary'] = '';
} else {
$hash['anniversary'] = $item['value']['year'] . '-' . $item['value']['month'] . '-' . $item['value']['mday'];
}
break;
case 'X-CHILDREN':
$hash['children'] = $item['value'];
break;
case 'X-SPOUSE':
$hash['spouse'] = $item['value'];
break;
}
}
/* Ensure we have a valid name field. */
if (empty($hash['name'])) {
/* If name is a composite field, it won't be present in the
* $this->fields array, so check for that as well. */
if (isset($this->map['name']) &&
is_array($this->map['name']) &&
!empty($this->map['name']['attribute'])) {
$fieldarray = array();
foreach ($this->map['name']['fields'] as $mapfields) {
$fieldarray[] = isset($hash[$mapfields]) ?
$hash[$mapfields] : '';
}
$hash['name'] = Turba::formatCompositeField($this->map['name']['format'], $fieldarray);
} else {
$hash['name'] = isset($hash['firstname']) ? $hash['firstname'] : '';
if (!empty($hash['lastname'])) {
$hash['name'] .= ' ' . $hash['lastname'];
}
$hash['name'] = trim($hash['name']);
}
}
return $hash;
}
/**
* Convert the contact to an ActiveSync contact message
*
* @param Turba_Object $object The turba object to convert
* @param array $options Options:
* - protocolversion: (float) The EAS version to support
* DEFAULT: 2.5
* - bodyprefs: (array) A BODYPREFERENCE array.
* DEFAULT: none (No body prefs enforced).
* - truncation: (integer) Truncate event body to this length
* DEFAULT: none (No truncation).
*
* @return Horde_ActiveSync_Message_Contact
*/
public function toASContact(Turba_Object $object, array $options = array())
{
$message = new Horde_ActiveSync_Message_Contact(array(
'logger' => $GLOBALS['injector']->getInstance('Horde_Log_Logger'),
'protocolversion' => $options['protocolversion'])
);
$hash = $object->getAttributes();
if (!isset($hash['lastname']) && isset($hash['name'])) {
$this->_guessName($hash);
}
// Ensure we have at least a good guess as to separate address fields.
// Not ideal, but EAS does not have a single "address" field so we must
// map "common" to either home or work. I choose home since
// work/non-personal installs will be more likely to have separated
// address fields.
if (!empty($hash['commonAddress'])) {
if (!isset($hash['commonStreet'])) {
$hash['commonStreet'] = $hash['commonHome'];
}
foreach (array('Address', 'Street', 'POBox', 'Extended', 'City', 'Province', 'PostalCode', 'Country') as $field) {
$hash['home' . $field] = $hash['common' . $field];
}
} else {
if (isset($hash['homeAddress']) && !isset($hash['homeStreet'])) {
$hash['homeStreet'] = $hash['homeAddress'];
}
if (isset($hash['workAddress']) && !isset($hash['workStreet'])) {
$hash['workStreet'] = $hash['workAddress'];
}
}
$haveDecodeHook = Horde::hookExists('decode_attribute', 'turba');
foreach ($hash as $field => $value) {
if ($haveDecodeHook) {
try {
$value = Horde::callHook(
'decode_attribute',
array($field, $value, $object),
'turba');
} catch (Turba_Exception $e) {
Horde::logMessage($e);
}
}
if (isset(self::$_asMap[$field])) {
try {
$message->{self::$_asMap[$field]} = $value;
} catch (InvalidArgumentException $e) {
}
continue;
}
switch ($field) {
case 'photo':
$message->picture = base64_encode($value);
break;
case 'homeCountry':
$message->homecountry = !empty($hash['homeCountryFree'])
? $hash['homeCountryFree']
: !empty($hash['homeCountry'])
? Horde_Nls::getCountryISO($hash['homeCountry'])
: null;
break;
case 'otherCountry':
$message->othercountry = !empty($hash['otherCountryFree'])
? $hash['otherCountryFree']
: !empty($hash['otherCountry'])
? Horde_Nls::getCountryISO($hash['otherCountry'])
: null;
break;
case 'workCountry':
$message->businesscountry = !empty($hash['workCountryFree'])
? $hash['workCountryFree']
: !empty($hash['workCountry'])
? Horde_Nls::getCountryISO($hash['workCountry'])
: null;
break;
case 'email':
$message->email1address = $value;
break;
case 'homeEmail':
$message->email2address = $value;
break;
case 'workEmail':
$message->email3address = $value;
break;
case 'emails':
$address = 1;
foreach (explode(',', $value) as $email) {
while ($address <= 3 &&
$message->{'email' . $address . 'address'}) {
$address++;
}
if ($address > 3) {
break;
}
$message->{'email' . $address . 'address'} = $email;
$address++;
}
break;
case 'category':
// Categories FROM horde are a simple string value, categories going BACK to horde are an array with 'value' and 'new' keys
$message->categories = explode(';', $value);
break;
case 'children':
// Children FROM horde are a simple string value. Even though EAS
// uses an array stucture to pass them, we pass as a single
// string since we can't assure what delimter the user will
// use and (at least in some languages) a comma can be used
// within a full name.
$message->children = array($value);
break;
case 'notes':
if ($options['protocolversion'] > Horde_ActiveSync::VERSION_TWOFIVE) {
$bp = $options['bodyprefs'];
$note = new Horde_ActiveSync_Message_AirSyncBaseBody();
// No HTML supported in Turba's notes. Always use plaintext.
$note->type = Horde_ActiveSync::BODYPREF_TYPE_PLAIN;
if (isset($bp[Horde_ActiveSync::BODYPREF_TYPE_PLAIN]['truncationsize'])) {
if (Horde_String::length($value) > $bp[Horde_ActiveSync::BODYPREF_TYPE_PLAIN]['truncationsize']) {
$note->data = Horde_String::substr($value, 0, $bp[Horde_ActiveSync::BODYPREF_TYPE_PLAIN]['truncationsize']);
$note->truncated = 1;
} else {
$note->data = $value;
}
$note->estimateddatasize = Horde_String::length($value);
}
$message->airsyncbasebody = $note;
} else {
// EAS 2.5
$message->body = $value;
$message->bodysize = strlen($message->body);
$message->bodytruncated = 0;
}
break;
case 'birthday':
case 'anniversary':
if (!empty($value) && $value != '0000-00-00') {
try {
$date = new Horde_Date($value);
} catch (Horde_Date_Exception $e) {
$message->$field = null;
}
// Some sanity checking to make sure the date was
// successfully parsed.
if ($date->month != 0) {
$message->$field = $date;
} else {
$message->$field = null;
}
} else {
$message->$field = null;
}
break;
}
}
if (empty($this->fileas)) {
$message->fileas = Turba::formatName($object);
}
return $message;
}
/**
* Convert an ActiveSync contact message into a hash suitable for
* importing via self::add().
*
* @param Horde_ActiveSync_Message_Contact $message The contact message
* object.
*
* @return array A contact hash.
*/
public function fromASContact(Horde_ActiveSync_Message_Contact $message)
{
$hash = array();
foreach (self::$_asMap as $turbaField => $asField) {
if (!$message->isGhosted($asField)) {
try {
$hash[$turbaField] = $message->{$asField};
} catch (InvalidArgumentException $e) {
}
}
}
/* Requires special handling */
try {
if ($message->getProtocolVersion() >= Horde_ActiveSync::VERSION_TWELVE) {
if (!empty($message->airsyncbasebody)) {
$hash['notes'] = $message->airsyncbasebody->data;
}
} else {
$hash['notes'] = $message->body;
}
} catch (InvalidArgumentException $e) {}
// picture ($message->picture *should* already be base64 encdoed)
if (!$message->isGhosted('picture')) {
$hash['photo'] = base64_decode($message->picture);
}
/* Email addresses */
$hash['emails'] = array();
if (!$message->isGhosted('email1address')) {
$e = Horde_Icalendar_Vcard::getBareEmail($message->email1address);
$hash['emails'][] = $hash['email'] = $e ? $e : '';
}
if (!$message->isGhosted('email2address')) {
$e = Horde_Icalendar_Vcard::getBareEmail($message->email2address);
$hash['emails'][] = $hash['homeEmail'] = $e ? $e : '';
}
if (!$message->isGhosted('email3address')) {
$e = Horde_Icalendar_Vcard::getBareEmail($message->email3address);
$hash['emails'][] = $hash['workEmail'] = $e ? $e : '';
}
$hash['emails'] = implode(',', $hash['emails']);
/* Categories */
if (is_array($message->categories) && count($message->categories)) {
$hash['category'] = implode(';', $message->categories);
} elseif (!$message->isGhosted('categories')) {
$hash['category'] = '';
}
/* Children */
if (is_array($message->children) && count($message->children)) {
// We use a comma as incoming delimiter as it's the most
// common even though it might be used withing a name string.
$hash['children'] = implode(', ', $message->children);
} elseif (!$message->isGhosted('children')) {
$hash['children'] = '';
}
/* Birthday and Anniversary */
if (!empty($message->birthday)) {
$bday = new Horde_Date($message->birthday);
$bday->setTimezone(date_default_timezone_get());
$hash['birthday'] = $bday->format('Y-m-d');
} elseif (!$message->isGhosted('birthday')) {
$hash['birthday'] = '';
}
if (!empty($message->anniversary)) {
$anniversary = new Horde_Date($message->anniversary);
$anniversary->setTimezone(date_default_timezone_get());
$hash['anniversary'] = $anniversary->format('Y-m-d');
} elseif (!$message->isGhosted('anniversary')) {
$hash['anniversary'] = '';
}
/* Countries */
include 'Horde/Nls/Countries.php';
if (!empty($message->homecountry)) {
if (!empty($this->map['homeCountryFree'])) {
$hash['homeCountryFree'] = $message->homecountry;
} else {
$country = array_search($message->homecountry, $countries);
if ($country === false) {
$country = $message->homecountry;
}
$hash['homeCountry'] = $country;
}
} elseif (!$message->isGhosted('homecountry')) {
$hash['homeCountry'] = '';
}
if (!empty($message->businesscountry)) {
if (!empty($this->map['workCountryFree'])) {
$hash['workCountryFree'] = $message->businesscountry;
} else {
$country = array_search($message->businesscountry, $countries);
if ($country === false) {
$country = $message->businesscountry;
}
$hash['workCountry'] = $country;
}
} elseif (!$message->isGhosted('businesscountry')) {
$hash['workCountry'] = '';
}
if (!empty($message->othercountry)) {
if (!empty($this->map['otherCountryFree'])) {
$hash['otherCountryFree'] = $message->othercountry;
} else {
$country = array_search($message->othercountry, $countries);
if ($country === false) {
$country = $message->othercountry;
}
$hash['otherCountry'] = $country;
}
} elseif (!$message->isGhosted('othercountry')) {
$hash['otherCountry'] = '';
}
return $hash;
}
/**
* Checks if the current user has the requested permissions on this
* address book.
*
* @param integer $perm The permission to check for.
*
* @return boolean True if the user has permission, otherwise false.
*/
public function hasPermission($perm)
{
$perms = $GLOBALS['injector']->getInstance('Horde_Perms');
return $perms->exists('turba:sources:' . $this->_name)
? $perms->hasPermission('turba:sources:' . $this->_name, $GLOBALS['registry']->getAuth(), $perm)
// Assume we have permissions if they're not explicitly set.
: true;
}
/**
* Return the name of this address book.
* (This is the key into the cfgSources array)
*
* @string Address book name
*/
public function getName()
{
return $this->_name;
}
/**
* Return the owner to use when searching or creating contacts in
* this address book.
*
* @return string Contact owner.
*/
public function getContactOwner()
{
return empty($this->_contact_owner)
? $this->_getContactOwner()
: $this->_contact_owner;
}
/**
* Override the contactOwner setting for this driver.
*
* @param string $owner The contact owner.
*/
public function setContactOwner($owner)
{
$this->_contact_owner = $owner;
}
/**
* Override the name setting for this driver.
*
* @param string $name The source name. This is the key into the
* $cfgSources array.
*/
public function setSourceName($name)
{
$this->_name = $name;
}
/**
* Return the owner to use when searching or creating contacts in
* this address book.
*
* @return string Contact owner.
*/
protected function _getContactOwner()
{
return $GLOBALS['registry']->getAuth();
}
/**
* Creates a new Horde_Share for this source type.
*
* @param string $share_name The share name
* @param array $params The params for the share.
*
* @return Horde_Share The share object.
*/
public function createShare($share_name, array $params)
{
// If the raw address book name is not set, use the share name
if (empty($params['params']['name'])) {
$params['params']['name'] = $share_name;
}
return Turba::createShare($share_name, $params);
}
/**
* Runs any actions after setting a new default tasklist.
*
* @param string $share The default share ID.
*/
public function setDefaultShare($share)
{
}
/**
* Creates an object key for a new object.
*
* @param array $attributes The attributes (in driver keys) of the
* object being added.
*
* @return string A unique ID for the new object.
*/
protected function _makeKey(array $attributes)
{
return hash('md5', mt_rand());
}
/**
* Creates an object UID for a new object.
*
* @return string A unique ID for the new object.
*/
protected function _makeUid()
{
return strval(new Horde_Support_Guid());
}
/**
* Initialize the driver.
*
* @throws Turba_Exception
*/
protected function _init()
{
}
/**
* Searches the address book with the given criteria and returns a
* filtered list of results. If the criteria parameter is an empty array,
* all records will be returned.
*
* @param array $criteria Array containing the search criteria.
* @param array $fields List of fields to return.
* @param array $blobFields Array of fields containing binary data.
* @param boolean $count_only Only return the count of matching entries,
* not the entries themselves.
*
* @return array Hash containing the search results.
* @throws Turba_Exception
*/
protected function _search(array $criteria, array $fields, array $blobFields = array(), $count_only = false)
{
throw new Turba_Exception(_("Searching is not available."));
}
/**
* Reads the given data from the address book and returns the results.
*
* @param string $key The primary key field to use.
* @param mixed $ids The ids of the contacts to load.
* @param string $owner Only return contacts owned by this user.
* @param array $fields List of fields to return.
* @param array $blobFields Array of fields containing binary data.
*
* @return array Hash containing the search results.
* @throws Turba_Exception
*/
protected function _read($key, $ids, $owner, array $fields,
array $blobFields = array())
{
throw new Turba_Exception(_("Reading contacts is not available."));
}
/**
* Adds the specified contact to the addressbook.
*
* @param array $attributes The attribute values of the contact.
* @param array $blob_fields TODO
*
* @throws Turba_Exception
*/
protected function _add(array $attributes, array $blob_fields = array())
{
throw new Turba_Exception(_("Adding contacts is not available."));
}
/**
* Deletes the specified contact from the addressbook.
*
* @param string $object_key TODO
* @param string $object_id TODO
*
* @throws Turba_Exception
*/
protected function _delete($object_key, $object_id)
{
throw new Turba_Exception(_("Deleting contacts is not available."));
}
/**
* Saves the specified object in the SQL database.
*
* @param Turba_Object $object The object to save
*
* @return string The object id, possibly updated.
* @throws Turba_Exception
*/
protected function _save(Turba_Object $object)
{
throw new Turba_Exception(_("Saving contacts is not available."));
}
/**
* Remove all entries owned by the specified user.
*
* @param string $user The user's data to remove.
*
* @throws Turba_Exception
*/
public function removeUserData($user)
{
throw new Turba_Exception_NotSupported(_("Removing user data is not supported in the current address book storage driver."));
}
/**
* Check if the passed in share is the default share for this source.
*
* @param Horde_Share_Object $share The share object.
* @param array $srcconfig The cfgSource entry for the share.
*
* @return boolean TODO
*/
public function checkDefaultShare(Horde_Share_Object $share, array $srcconfig)
{
$params = @unserialize($share->get('params'));
if (!isset($params['default'])) {
$params['default'] = ($params['name'] == $GLOBALS['registry']->getAuth());
$share->set('params', serialize($params));
$share->save();
}
return $params['default'];
}
/* Countable methods. */
/**
* Returns the number of contacts of the current user in this address book.
*
* @return integer The number of contacts that the user owns.
* @throws Turba_Exception
*/
public function count()
{
if (is_null($this->_count)) {
$this->_count = count(
$this->_search(array('AND' => array(
array('field' => $this->toDriver('__owner'),
'op' => '=',
'test' => $this->getContactOwner()))),
array($this->toDriver('__key')))
);
}
return $this->_count;
}
/**
* Helper function for guessing name parts from a single name string.
*
* @param array $hash The attributes array.
*/
protected function _guessName(&$hash)
{
if (($pos = strpos($hash['name'], ',')) !== false) {
// Assume Last, First
$hash['lastname'] = Horde_String::substr($hash['name'], 0, $pos);
$hash['firstname'] = trim(Horde_String::substr($hash['name'], $pos + 1));
} elseif (($pos = Horde_String::rpos($hash['name'], ' ')) !== false) {
// Assume everything after last space as lastname
$hash['lastname'] = trim(Horde_String::substr($hash['name'], $pos + 1));
$hash['firstname'] = Horde_String::substr($hash['name'], 0, $pos);
} else {
$hash['lastname'] = $hash['name'];
$hash['firstname'] = '';
}
}
}
turba-4.1.3/lib/Exception.php 0000664 0001750 0001750 00000000704 12233762701 014161 0 ustar jan jan
* @category Horde
* @license http://www.horde.org/licenses/apache ASL
* @package Turba
*/
class Turba_Exception extends Horde_Exception_Wrapped
{
}
turba-4.1.3/lib/List.php 0000664 0001750 0001750 00000016425 12233762702 013146 0 ustar jan jan
* @author Jon Parise
* ascending - (boolean) Sort direction.
* field - (string) Sort field.
*
*/
public function sort($order = null)
{
if (!$order) {
$order = array(
array(
'ascending' => true,
'field' => 'lastname'
)
);
}
$need_lastname = $need_firstname = false;
$name_format = $GLOBALS['prefs']->getValue('name_format');
$name_sort = $GLOBALS['prefs']->getValue('name_sort');
foreach ($order as &$field) {
if ($field['field'] == 'name') {
if ($name_sort == 'last_first') {
$field['field'] = 'lastname';
} elseif ($name_sort == 'first_last') {
$field['field'] = 'firstname';
}
}
if ($field['field'] == 'lastname') {
$field['field'] = '__lastname';
$need_lastname = true;
break;
}
if ($field['field'] == 'firstname') {
$field['field'] = '__firstname';
$need_firstname = true;
break;
}
}
if ($need_firstname || $need_lastname) {
$sorted_objects = array();
foreach ($this->objects as $key => $object) {
$name = $object->getValue('name');
$firstname = $object->getValue('firstname');
$lastname = $object->getValue('lastname');
if (!$lastname) {
$lastname = Turba::guessLastname($name);
}
if (!$firstname) {
switch ($name_format) {
case 'last_first':
$firstname = preg_replace('/' . preg_quote($lastname, '/') . ',\s*/', '', $name);
break;
case 'first_last':
$firstname = preg_replace('/\s+' . preg_quote($lastname, '/') . '/', '', $name);
break;
default:
$firstname = preg_replace('/\s*' . preg_quote($lastname, '/') . '(,\s*)?/', '', $name);
break;
}
}
$object->setValue('__lastname', $lastname);
$object->setValue('__firstname', $firstname);
$sorted_objects[$key] = $object;
}
} else {
$sorted_objects = $this->objects;
}
$this->_usortCriteria = $order;
/* Exceptions thrown inside a sort incorrectly cause an error. See
* Bug #9202. */
@usort($sorted_objects, array($this, '_cmp'));
$this->objects = $sorted_objects;
}
/**
* Usort helper function.
*
* Compares two Turba_Objects based on the member variable
* $_usortCriteria, taking care to sort numerically if it is an integer
* field.
*
* @param Turba_Object $a The first Turba_Object to compare.
* @param Turba_Object $b The second Turba_Object to compare.
*
* @return integer Comparison of the two field values.
*/
protected function _cmp(Turba_Object $a, Turba_Object $b)
{
foreach ($this->_usortCriteria as $field) {
// Set the comparison type based on the type of attribute we're
// sorting by.
$sortmethod = 'text';
if (isset($GLOBALS['attributes'][$field['field']])) {
$f = $GLOBALS['attributes'][$field['field']];
if (!empty($f['cmptype'])) {
$sortmethod = $f['cmptype'];
} elseif (in_array($f['type'], array('int', 'intlist', 'number'))) {
$sortmethod = 'int';
}
}
$f = $field['field'];
switch ($sortmethod) {
case 'int':
$result = ($a->getValue($f) > $b->getValue($f)) ? 1 : -1;
break;
case 'text':
if (!isset($a->sortValue[$f])) {
$a->sortValue[$f] = Horde_String::lower($a->getValue($f), true, 'UTF-8');
}
if (!isset($b->sortValue[$f])) {
$b->sortValue[$f] = Horde_String::lower($b->getValue($f), true, 'UTF-8');
}
// Use strcoll for locale-safe comparisons.
$result = strcoll($a->sortValue[$f], $b->sortValue[$f]);
break;
}
if (!$field['ascending']) {
$result = -$result;
}
if ($result != 0) {
return $result;
}
}
return 0;
}
/* Countable methods. */
public function count()
{
return count($this->objects);
}
}
turba-4.1.3/lib/Object.php 0000664 0001750 0001750 00000036360 12233762702 013441 0 ustar jan jan
* @author Jon Parise unable to bind as ' . htmlspecialchars($params['user']) . ' to LDAP server
'; ldap_close($ldap); $ldap = ''; } elseif (empty($params['user']) && !ldap_bind($ldap)) { $ret .= "unable to bind anonymously to LDAP server
\n"; ldap_close($ldap); $ldap = ''; } if ($ldap) { $result = ldap_search($ldap, $params['basedn'], $params['filter']); if ($result) { $ret .= 'search returned ' . ldap_count_entries($ldap, $result) . " entries
\n"; $info = ldap_get_entries($ldap, $result); for ($i = 0; $i < $info['count']; ++$i) { $ret .= 'dn is: ' . $info[$i]['dn'] . '
' .
'first cn entry is: ' . $info[$i]['cn'][0] . '
' .
'first mail entry is: ' . $info[$i]['mail'][0] . '
(only first 10 entries displayed)
'; break; } } } else { $ret .= 'unable to search LDAP server
'; } } } else { $ret .= 'unable to connect to LDAP server
'; } return $ret; } } turba-4.1.3/lib/Turba.php 0000664 0001750 0001750 00000056756 12233762702 013323 0 ustar jan jan * @author Jon Parise