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