620 lines
20 KiB
PHP
620 lines
20 KiB
PHP
<?php
|
|
/**
|
|
* Copyright 2000-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.
|
|
*
|
|
* @category Horde
|
|
* @copyright 2000-2017 Horde LLC
|
|
* @license http://www.horde.org/licenses/apache ASL
|
|
* @package Turba
|
|
*/
|
|
|
|
/**
|
|
* A base implementation for Turba objects - people, groups, restaurants, etc.
|
|
*
|
|
* @author Chuck Hagenbuch <chuck@horde.org>
|
|
* @author Jon Parise <jon@csh.rit.edu>
|
|
* @category Horde
|
|
* @copyright 2000-2017 Horde LLC
|
|
* @license http://www.horde.org/licenses/apache ASL
|
|
* @package Turba
|
|
*/
|
|
class Turba_Object
|
|
{
|
|
/**
|
|
* Underlying driver.
|
|
*
|
|
* @var Turba_Driver
|
|
*/
|
|
public $driver;
|
|
|
|
/**
|
|
* Hash of attributes for this contact.
|
|
*
|
|
* @var array
|
|
*/
|
|
public $attributes;
|
|
|
|
/**
|
|
* Keeps the normalized values of sort columns.
|
|
*
|
|
* @var array
|
|
*/
|
|
public $sortValue = array();
|
|
|
|
/**
|
|
* Any additional options.
|
|
*
|
|
* @var boolean
|
|
*/
|
|
protected $_options = array();
|
|
|
|
/**
|
|
* Reference to this object's VFS instance.
|
|
*
|
|
* @var VFS
|
|
*/
|
|
protected $_vfs;
|
|
|
|
/**
|
|
* Local cache of available email addresses. Needed to ensure we
|
|
* populate the email field correctly. See See Bug: 12955 and Bug: 14046.
|
|
* A hash with turba attribute names as key.
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $_emailFields = array();
|
|
|
|
/**
|
|
* Constructs a new Turba_Object object.
|
|
*
|
|
* @param Turba_Driver $driver The source that this object came from.
|
|
* @param array $attributes Hash of attributes for this object.
|
|
* @param array $options Hash of options for this object. @since
|
|
* Turba 4.2
|
|
*/
|
|
public function __construct(Turba_Driver $driver,
|
|
array $attributes = array(),
|
|
array $options = array())
|
|
{
|
|
$this->driver = $driver;
|
|
$this->attributes = $attributes;
|
|
$this->attributes['__type'] = 'Object';
|
|
$this->_options = $options;
|
|
}
|
|
|
|
/**
|
|
* Returns a key-value hash containing all properties of this object.
|
|
*
|
|
* @return array All properties of this object.
|
|
*/
|
|
public function getAttributes()
|
|
{
|
|
return $this->attributes;
|
|
}
|
|
|
|
/**
|
|
* Returns the name of the address book that this object is from.
|
|
*/
|
|
public function getSource()
|
|
{
|
|
return $this->driver->getName();
|
|
}
|
|
|
|
/**
|
|
* Get a fully qualified key for this contact.
|
|
*
|
|
* @param string $delimiter Delimiter for the parts of the key, defaults to ':'.
|
|
*
|
|
* @return string Fully qualified contact id.
|
|
*/
|
|
public function getGuid($delimiter = ':')
|
|
{
|
|
return 'turba' . $delimiter . $this->getSource() . $delimiter . $this->getValue('__uid');
|
|
}
|
|
|
|
/**
|
|
* Returns the value of the specified attribute.
|
|
*
|
|
* @param string $attribute The attribute to retrieve.
|
|
*
|
|
* @return mixed The value of $attribute, an array (for photo type)
|
|
* or the empty string.
|
|
*/
|
|
public function getValue($attribute)
|
|
{
|
|
global $attributes, $injector;
|
|
|
|
if (isset($this->attributes[$attribute]) &&
|
|
($hooks = $injector->getInstance('Horde_Core_Hooks')) &&
|
|
$hooks->hookExists('decode_attribute', 'turba')) {
|
|
try {
|
|
return $hooks->callHook(
|
|
'decode_attribute',
|
|
'turba',
|
|
array($attribute, $this->attributes[$attribute], $this)
|
|
);
|
|
} catch (Turba_Exception $e) {}
|
|
} elseif (isset($this->driver->map[$attribute]) &&
|
|
is_array($this->driver->map[$attribute])) {
|
|
$args = array();
|
|
foreach ($this->driver->map[$attribute]['fields'] as $field) {
|
|
$args[] = $this->getValue($field);
|
|
}
|
|
return Turba::formatCompositeField($this->driver->map[$attribute]['format'], $args);
|
|
} elseif (!isset($this->attributes[$attribute])) {
|
|
if (isset($attributes[$attribute]) &&
|
|
($attributes[$attribute]['type'] == 'Turba:TurbaTags') &&
|
|
($uid = $this->getValue('__uid'))) {
|
|
$this->synchronizeTags($injector->getInstance('Turba_Tagger')->getTags($uid, 'contact'));
|
|
} else {
|
|
return null;
|
|
}
|
|
} elseif (isset($attributes[$attribute]) &&
|
|
($attributes[$attribute]['type'] == 'image')) {
|
|
return empty($this->attributes[$attribute])
|
|
? null
|
|
: array(
|
|
'load' => array(
|
|
'data' => $this->attributes[$attribute],
|
|
'file' => basename(Horde::getTempFile('horde_form_', false, '', false, true))
|
|
)
|
|
);
|
|
}
|
|
|
|
return $this->attributes[$attribute];
|
|
}
|
|
|
|
/**
|
|
* Sets the value of the specified attribute.
|
|
*
|
|
* @param string $attribute The attribute to set.
|
|
* @param string $value The value of $attribute.
|
|
*/
|
|
public function setValue($attribute, $value)
|
|
{
|
|
global $injector, $attributes;
|
|
|
|
$hooks = $injector->getInstance('Horde_Core_Hooks');
|
|
|
|
if ($hooks->hookExists('encode_attribute', 'turba')) {
|
|
try {
|
|
$value = $hooks->callHook(
|
|
'encode_attribute',
|
|
'turba',
|
|
array(
|
|
$attribute,
|
|
$value,
|
|
isset($this->attributes[$attribute]) ? $this->attributes[$attribute] : null,
|
|
$this
|
|
)
|
|
);
|
|
} catch (Turba_Exception $e) {}
|
|
}
|
|
|
|
// If we don't know the attribute, it's not a private attribute,
|
|
// and it's an email field, save it in case we need to populate an email
|
|
// field on save.
|
|
if (!isset($this->driver->map[$attribute]) && strpos($attribute, '__') === false) {
|
|
if (isset($attributes[$attribute]) &&
|
|
$attributes[$attribute]['type'] == 'email') {
|
|
$this->_emailFields[$attribute] = $value;
|
|
}
|
|
return;
|
|
}
|
|
|
|
$this->attributes[$attribute] = $value;
|
|
}
|
|
|
|
/**
|
|
* Determines whether or not the object has a value for the specified
|
|
* attribute.
|
|
*
|
|
* @param string $attribute The attribute to check.
|
|
*
|
|
* @return boolean Whether or not there is a value for $attribute.
|
|
*/
|
|
public function hasValue($attribute)
|
|
{
|
|
if (isset($this->driver->map[$attribute]) &&
|
|
is_array($this->driver->map[$attribute])) {
|
|
foreach ($this->driver->map[$attribute]['fields'] as $field) {
|
|
if ($this->hasValue($field)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
} else {
|
|
return !is_null($this->getValue($attribute));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Syncronizes tags from the tagging backend with the contacts storage
|
|
* backend, if necessary.
|
|
*
|
|
* @param array $tags Tags from the tagging backend.
|
|
*/
|
|
public function synchronizeTags(array $tags)
|
|
{
|
|
if (!is_null($internaltags = $this->getValue('__internaltags'))) {
|
|
$internaltags = unserialize($internaltags);
|
|
usort($tags, 'strcoll');
|
|
if (array_diff($internaltags, $tags)) {
|
|
$GLOBALS['injector']->getInstance('Turba_Tagger')->replaceTags(
|
|
$this->getValue('__uid'),
|
|
$internaltags,
|
|
$this->driver->getContactOwner(),
|
|
'contact'
|
|
);
|
|
}
|
|
$this->setValue('__tags', implode(', ', $internaltags));
|
|
} else {
|
|
$this->setValue('__tags', implode(', ', $tags));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns the timestamp of the last modification, whether this was the
|
|
* creation or editing of the object and stores it as the attribute
|
|
* __modified. The value is cached for the lifetime of the object.
|
|
*
|
|
* @return integer The timestamp of the last modification or zero.
|
|
*/
|
|
public function lastModification()
|
|
{
|
|
$time = $this->getValue('__modified');
|
|
if (!is_null($time)) {
|
|
return $time;
|
|
}
|
|
if (!$this->getValue('__uid')) {
|
|
$this->setValue('__modified', 0);
|
|
return 0;
|
|
}
|
|
$time = 0;
|
|
try {
|
|
$log = $GLOBALS['injector']
|
|
->getInstance('Horde_History')
|
|
->getHistory($this->getGuid());
|
|
foreach ($log as $entry) {
|
|
if ($entry['action'] == 'add' || $entry['action'] == 'modify') {
|
|
|
|
$time = max($time, $entry['ts']);
|
|
}
|
|
}
|
|
} catch (Exception $e) {}
|
|
$this->setValue('__modified', $time);
|
|
|
|
return $time;
|
|
}
|
|
|
|
/**
|
|
* Merges another contact into this one by filling empty fields of this
|
|
* contact with values from the other.
|
|
*
|
|
* @param Turba_Object $contact Another contact.
|
|
*/
|
|
public function merge(Turba_Object $contact)
|
|
{
|
|
foreach (array_keys($contact->attributes) as $attribute) {
|
|
if (!$this->hasValue($attribute) && $contact->hasValue($attribute)) {
|
|
$this->setValue($attribute, $contact->getValue($attribute));
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns history information about this contact.
|
|
*
|
|
* @return array A hash with the optional entries 'created' and 'modified'
|
|
* and human readable history information as the values.
|
|
*/
|
|
public function getHistory()
|
|
{
|
|
if (!$this->getValue('__uid')) {
|
|
return array();
|
|
}
|
|
$history = array();
|
|
try {
|
|
$log = $GLOBALS['injector']
|
|
->getInstance('Horde_History')
|
|
->getHistory($this->getGuid());
|
|
foreach ($log as $entry) {
|
|
if ($entry['action'] == 'add' || $entry['action'] == 'modify') {
|
|
if ($GLOBALS['registry']->getAuth() != $entry['who']) {
|
|
$by = sprintf(_("by %s"), Turba::getUserName($entry['who']));
|
|
} else {
|
|
$by = _("by me");
|
|
}
|
|
$history[$entry['action'] == 'add' ? 'created' : 'modified']
|
|
= strftime($GLOBALS['prefs']->getValue('date_format'), $entry['ts'])
|
|
. ' '
|
|
. date($GLOBALS['prefs']->getValue('twentyFour') ? 'G:i' : 'g:i a', $entry['ts'])
|
|
. ' '
|
|
. htmlspecialchars($by);
|
|
}
|
|
}
|
|
} catch (Exception $e) {
|
|
return array();
|
|
}
|
|
|
|
return $history;
|
|
}
|
|
|
|
/**
|
|
* Returns true if this object is a group of multiple contacts.
|
|
*
|
|
* @return boolean True if this object is a group of multiple contacts.
|
|
*/
|
|
public function isGroup()
|
|
{
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Returns true if this object is editable by the current user.
|
|
*
|
|
* @return boolean Whether or not the current user can edit this object
|
|
*/
|
|
public function isEditable()
|
|
{
|
|
return $this->driver->hasPermission(Horde_Perms::EDIT);
|
|
}
|
|
|
|
/**
|
|
* Returns whether or not the current user has the requested permission.
|
|
*
|
|
* @param integer $perm The permission to check.
|
|
*
|
|
* @return boolean True if user has the permission.
|
|
*/
|
|
public function hasPermission($perm)
|
|
{
|
|
return $this->driver->hasPermission($perm);
|
|
}
|
|
|
|
/**
|
|
* Contact url.
|
|
*
|
|
* @param string $view The view for the url
|
|
* @param boolean $full Generate a full url?
|
|
*
|
|
* @return string
|
|
*/
|
|
public function url($view = null, $full = false)
|
|
{
|
|
$url = Horde::url('contact.php', $full)->add(array(
|
|
'source' => $this->driver->getName(),
|
|
'key' => $this->getValue('__key')
|
|
));
|
|
|
|
if (!is_null($view)) {
|
|
$url->add('view', $view);
|
|
}
|
|
|
|
return $url;
|
|
}
|
|
|
|
/**
|
|
* Saves a file into the VFS backend associated with this object.
|
|
*
|
|
* @param array $info A hash with the file information as returned from a
|
|
* Horde_Form_Type_file.
|
|
* @throws Turba_Exception
|
|
*/
|
|
public function addFile(array $info)
|
|
{
|
|
if (!$this->getValue('__uid')) {
|
|
throw new Turba_Exception('VFS not supported for this object.');
|
|
}
|
|
|
|
$vfs = $this->vfsInit();
|
|
|
|
$dir = Turba::VFS_PATH . '/' . $this->getValue('__uid');
|
|
$file = $info['name'];
|
|
while ($vfs->exists($dir, $file)) {
|
|
if (preg_match('/(.*)\[(\d+)\](\.[^.]*)?$/', $file, $match)) {
|
|
$file = $match[1] . '[' . ++$match[2] . ']' . $match[3];
|
|
} else {
|
|
$dot = strrpos($file, '.');
|
|
if ($dot === false) {
|
|
$file .= '[1]';
|
|
} else {
|
|
$file = substr($file, 0, $dot) . '[1]' . substr($file, $dot);
|
|
}
|
|
}
|
|
}
|
|
try {
|
|
$vfs->write($dir, $file, $info['tmp_name'], true);
|
|
} catch (Horde_Vfs_Exception $e) {
|
|
throw new Turba_Exception($e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Deletes a file from the VFS backend associated with this object.
|
|
*
|
|
* @param string $file The file name.
|
|
* @throws Turba_Exception
|
|
*/
|
|
public function deleteFile($file)
|
|
{
|
|
if (!$this->getValue('__uid')) {
|
|
throw new Turba_Exception('VFS not supported for this object.');
|
|
}
|
|
|
|
try {
|
|
$this->vfsInit()->deleteFile(Turba::VFS_PATH . '/' . $this->getValue('__uid'), $file);
|
|
} catch (Horde_Vfs_Exception $e) {
|
|
throw new Turba_Exception($e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Deletes all files from the VFS backend associated with this object.
|
|
*
|
|
* @throws Turba_Exception
|
|
*/
|
|
public function deleteFiles()
|
|
{
|
|
if (!$this->getValue('__uid')) {
|
|
throw new Turba_Exception('VFS not supported for this object.');
|
|
}
|
|
|
|
$vfs = $this->vfsInit();
|
|
|
|
if ($vfs->exists(Turba::VFS_PATH, $this->getValue('__uid'))) {
|
|
try {
|
|
$vfs->deleteFolder(Turba::VFS_PATH, $this->getValue('__uid'), true);
|
|
} catch (Horde_Vfs_Exception $e) {
|
|
throw new Turba_Exception($e);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns all files from the VFS backend associated with this object.
|
|
*
|
|
* @return array A list of hashes with file informations.
|
|
*/
|
|
public function listFiles()
|
|
{
|
|
if ($this->getValue('__uid')) {
|
|
try {
|
|
$vfs = $this->vfsInit();
|
|
if ($vfs->exists(Turba::VFS_PATH, $this->getValue('__uid'))) {
|
|
return $vfs->listFolder(Turba::VFS_PATH . '/' . $this->getValue('__uid'));
|
|
}
|
|
} catch (Turba_Exception $e) {}
|
|
}
|
|
|
|
return array();
|
|
}
|
|
|
|
/**
|
|
* Returns a link to display and download a file from the VFS backend
|
|
* associated with this object.
|
|
*
|
|
* @param string $file The file name.
|
|
*
|
|
* @return string The HTML code of the generated link.
|
|
*/
|
|
public function vfsDisplayUrl($file)
|
|
{
|
|
global $registry;
|
|
|
|
$mime_part = new Horde_Mime_Part();
|
|
$mime_part->setType(Horde_Mime_Magic::extToMime($file['type']));
|
|
$viewer = $GLOBALS['injector']->getInstance('Horde_Core_Factory_MimeViewer')->create($mime_part);
|
|
|
|
// We can always download files.
|
|
$url_params = array(
|
|
'actionID' => 'download_file',
|
|
'file' => $file['name'],
|
|
'type' => $file['type'],
|
|
'source' => $this->driver->getName(),
|
|
'key' => $this->getValue('__key')
|
|
);
|
|
$dl = Horde::link($registry->downloadUrl($file['name'], $url_params), $file['name']) . Horde_Themes_Image::tag('download.png', array('alt' => _("Download"))) . '</a>';
|
|
|
|
// Let's see if we can view this one, too.
|
|
if ($viewer && !($viewer instanceof Horde_Mime_Viewer_Default)) {
|
|
$url = Horde::url('view.php')
|
|
->add($url_params)
|
|
->add('actionID', 'view_file');
|
|
$link = Horde::link($url, $file['name'], null, '_blank') . $file['name'] . '</a>';
|
|
} else {
|
|
$link = $file['name'];
|
|
}
|
|
|
|
return $link . ' ' . $dl;
|
|
}
|
|
|
|
/**
|
|
* Returns a link to display, download, and delete a file from the VFS
|
|
* backend associated with this object.
|
|
*
|
|
* @param string $file The file name.
|
|
*
|
|
* @return string The HTML code of the generated link.
|
|
*/
|
|
public function vfsEditUrl($file)
|
|
{
|
|
$delform = '<form action="' .
|
|
Horde::url('deletefile.php') .
|
|
'" style="display:inline" method="post">' .
|
|
Horde_Util::formInput() .
|
|
'<input type="hidden" name="file" value="' . htmlspecialchars($file['name']) . '" />' .
|
|
'<input type="hidden" name="source" value="' . htmlspecialchars($this->driver->getName()) . '" />' .
|
|
'<input type="hidden" name="key" value="' . htmlspecialchars($this->getValue('__key')) . '" />' .
|
|
'<input type="image" class="img" src="' . Horde_Themes::img('delete.png') . '" />' .
|
|
'</form>';
|
|
|
|
return $this->vfsDisplayUrl($file) . ' ' . $delform;
|
|
}
|
|
|
|
/**
|
|
* Saves the current state of the object to the storage backend.
|
|
*
|
|
* @throws Turba_Exception
|
|
*/
|
|
public function store()
|
|
{
|
|
$this->_ensureEmail();
|
|
return $this->setValue('__key', $this->driver->save($this));
|
|
}
|
|
|
|
/**
|
|
* Loads the VFS configuration and initializes the VFS backend.
|
|
*
|
|
* @return Horde_Vfs A VFS object.
|
|
* @throws Turba_Exception
|
|
*/
|
|
public function vfsInit()
|
|
{
|
|
if (!isset($this->_vfs)) {
|
|
try {
|
|
$this->_vfs = $GLOBALS['injector']->getInstance('Horde_Core_Factory_Vfs')->create('documents');
|
|
} catch (Horde_Exception $e) {
|
|
throw new Turba_Exception($e);
|
|
}
|
|
}
|
|
|
|
return $this->_vfs;
|
|
}
|
|
|
|
/**
|
|
* Ensure we have an email address set, if available. Needed to cover the
|
|
* case where a contact might have been imported via vCard with email TYPEs
|
|
* that do not match the configured attributes for this source. E.g., the
|
|
* vCard contains a TYPE=HOME but we only have the generic 'email' field
|
|
* available.
|
|
*
|
|
* @return [type] [description]
|
|
*/
|
|
protected function _ensureEmail()
|
|
{
|
|
global $attributes;
|
|
|
|
// If an email type attribute is not known to this object's driver map
|
|
// then attempt to fill in any email attributes we DO know about that
|
|
// are currently empty. Not ideal, but if a client is sending unknown
|
|
// email fields, we have no way of knowing where to put them and this
|
|
// is better than dropping them.
|
|
foreach ($this->_emailFields as $attribute => $email) {
|
|
if (empty($this->driver->map[$attribute]) && $attribute != 'emails') {
|
|
foreach ($this->driver->map as $driver_att => $driver_value) {
|
|
if ($attributes[$driver_att]['type'] == 'email' &&
|
|
empty($this->attributes[$driver_att])) {
|
|
$this->attributes[$driver_att] = $email;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|