WIP contact sharing

This commit is contained in:
Ralf Becker 2020-10-13 16:54:35 +02:00
parent 855d6defc9
commit 1f7ce98c50
7 changed files with 132 additions and 3 deletions

View File

@ -2070,6 +2070,30 @@ class addressbook_ui extends addressbook_bo
{
if (is_array($content))
{
// sync $content['shared'] with $content['shared_values']
foreach($content['shared'] as $key => $shared)
{
$shared_value = $shared['shared_id'].':'.$shared['shared_with'].':'.$shared['shared_by'].':'.$shared['shared_writable'];
if (($k = array_search($shared_value, $content['shared_values'])) === false)
{
unset($content['shared'][$key]);
}
else
{
unset($content['shared_values'][$k]);
}
}
foreach($content['shared_values'] as $account_id)
{
$content['shared'][] = [
'shared_with' => $account_id,
'shared_by' => $this->user,
'shared_at' => new Api\DateTime(),
'shared_writable' => (int)(bool)$content['shared_writable'],
];
}
unset($content['shared_values']);
$button = @key($content['button']);
unset($content['button']);
$content['private'] = (int) ($content['owner'] && substr($content['owner'],-1) == 'p');
@ -2403,6 +2427,20 @@ class addressbook_ui extends addressbook_bo
}
}
}
// set $content[shared_options/_values] from $content[shared]
$content['shared_options'] = [];
foreach((array)$content['shared'] as $shared)
{
$content['shared_options'][$shared['shared_id'].':'.$shared['shared_with'].':'.$shared['shared_by'].':'.$shared['shared_writable']] = [
'label' => Accounts::username($shared['shared_with']),
'title' => lang('%1 shared this contact on %2 with %3 %4',
Accounts::username($shared['shared_by']), Api\DateTime::to($shared['shared_at']),
Accounts::username($shared['shared_with']), $shared['shared_writable'] ? lang('writable') : lang('readonly')),
'icon' => $shared['shared_writable'] ? 'edit' : 'view',
];
}
$content['shared_values'] = array_keys($content['shared_options']);
if ($content['id'])
{
// last and next calendar date

View File

@ -8,6 +8,7 @@
%1 public keys added. addressbook de %1 öffentliche Schlüssel gespeichert.
%1 records imported addressbook de %1 Datensätze importiert
%1 records read (not yet imported, you may go %2back%3 and uncheck test import) addressbook de %1 Datensätze gelesen (noch nicht importiert, sie können %2zurück%3 gehen und Test-Import ausschalten)
%1 shared this contact on %2 with %3 %4 addressbook de %1 teilte diesen Kontakt am %2 mit %3 %4
%1 starts with '%2' addressbook de %1 beginnt mit '%2'
%s please calculate the result addressbook de %s Bitte berechnen Sie das Ergebnis
(e.g. 1969) addressbook de (z.B. 1969)
@ -454,6 +455,7 @@ send succeeded to %1 common de erfolgreich versandt an
seperator addressbook de Feldtrenner
set full name and file as field in contacts of all users (either all or only empty values) admin de Setzt vollen Namen und eigene Sortierung in Kontakten aller Benutzer (entweder alle oder nur leere Werte)
set only full name addressbook de Nur vollen Namen setzen
shared with addressbook de Geteilt mit
should the columns photo and home address always be displayed, even if they are empty. addressbook de Sollen die Spalten Foto und Privatadresse immer angezeigt werden, auch wenn sie leer sind?
show addressbook de Anzeigen
show active accounts addressbook de Zeigt nur aktive Benutzer an

View File

@ -8,6 +8,7 @@
%1 public keys added. addressbook en %1 public keys added.
%1 records imported addressbook en %1 records imported.
%1 records read (not yet imported, you may go %2back%3 and uncheck test import) addressbook en %1 records read. Not yet imported, you may go %2back%3 and un-check Test import.
%1 shared this contact on %2 with %3 %4 addressbook en %1 shared this contact on %2 with %3 %4
%1 starts with '%2' addressbook en %1 starts with '%2'
%s please calculate the result addressbook en %s please calculate the result
(e.g. 1969) addressbook en (e.g. 1969)
@ -454,6 +455,7 @@ send succeeded to %1 common en Send succeeded to %1
seperator addressbook en Separator
set full name and file as field in contacts of all users (either all or only empty values) admin en Set full name and 'fileas' field in contacts of all users. Either all or only empty values.
set only full name addressbook en Set only full name
shared with addressbook en Shared with
should the columns photo and home address always be displayed, even if they are empty. addressbook en Are photo and home address always displayed, even if columns are empty.
show addressbook en Show
show active accounts addressbook en Show active accounts

View File

@ -143,6 +143,11 @@
</hbox>
<description/>
</row>
<row>
<description for="shared" value="Shared with"/>
<taglist-account id="shared_values" multiple="true" select_options="@shared_options" span="4"/>
<checkbox id="shared_writable" label="writable" statustext="Create new shares writable"/>
</row>
</rows>
</grid>
</template>

View File

@ -1233,6 +1233,19 @@ class Contacts extends Contacts\Storage
$access = ($grants[$owner] & $needed) &&
(!$contact['private'] || ($grants[$owner] & Acl::PRIVAT) || in_array($owner,$memberships));
}
// check if we might have access via sharing (not for delete)
if ($access === false && !empty($contact['shared']) && $needed != Acl::DELETE)
{
foreach($contact['shared'] as $shared)
{
if (isset($grants[$shared['shared_with']]) && ($shared['shared_writable'] || !($needed & Acl::EDIT)))
{
$access = true;
error_log(__METHOD__."($needed,$contact[id],$deny_account_delete,$user) shared=".json_encode($shared)." returning ".array2string($access));
break;
}
}
}
//error_log(__METHOD__."($needed,$contact[id],$deny_account_delete,$user) returning ".array2string($access));
return $access;
}

View File

@ -62,6 +62,8 @@ class Sql extends Api\Storage
const EXTRA_TABLE = 'egw_addressbook_extra';
const EXTRA_VALUE = 'contact_value';
const SHARED_TABLE = 'egw_addressbook_shared';
/**
* Constructor
*
@ -497,6 +499,11 @@ class Sql extends Api\Storage
unset($filter['cat_id']);
}
// SQL to get all shared contacts to be OR-ed into ACL filter
// ToDo: do we need a sharing filter for $ignore_acl
$shared_sql = 'contact_id IN (SELECT contact_id FROM '.self::SHARED_TABLE.' WHERE '.
$this->db->expression(self::SHARED_TABLE, ['shared_with' => $filter['owner'] ?? array_keys($this->grants)]).')';
// add filter for read ACL in sql, if user is NOT the owner of the addressbook
if (isset($this->grants) && !$ignore_acl &&
!(isset($filter['owner']) && $filter['owner'] == $GLOBALS['egw_info']['user']['account_id']))
@ -524,18 +531,21 @@ class Sql extends Api\Storage
if (!array_intersect((array)$filter['owner'],array_keys($this->grants)))
{
if (!isset($groupmember_sql)) return false;
$filter[] = substr($groupmember_sql,4);
$filter[] = '('.substr($groupmember_sql,4)." OR $shared_sql)";
unset($filter['owner']);
}
// for an owner filter, which does NOT include current user, filter out private entries
elseif (!in_array($GLOBALS['egw_info']['user']['account_id'], (array)$filter['owner']))
{
$filter['private'] = 0;
$filter[] = '('.$this->db->expression($this->table_name, $this->table_name.'.', ['contact_owner' => $filter['owner'], 'contact_private' => 0]).
" OR $shared_sql)";
unset($filter['owner']);
}
// if multiple addressbooks (incl. current owner) are searched, we need full acl filter
elseif(is_array($filter['owner']) && count($filter['owner']) > 1)
{
$filter[] = "($this->table_name.contact_owner=".(int)$GLOBALS['egw_info']['user']['account_id'].
" OR $shared_sql".
" OR contact_private=0 AND $this->table_name.contact_owner IN (".
implode(',',array_keys($this->grants)).") $groupmember_sql OR $this->table_name.contact_owner IS NULL)";
}
@ -547,6 +557,7 @@ class Sql extends Api\Storage
$filter[] = $this->table_name.'.contact_owner != 0'; // in case there have been accounts in sql previously
}
$filter[] = "($this->table_name.contact_owner=".(int)$GLOBALS['egw_info']['user']['account_id'].
" OR $shared_sql".
($this->grants ? " OR contact_private=0 AND $this->table_name.contact_owner IN (".
implode(',',array_keys($this->grants)).")" : '').
$groupmember_sql." OR $this->table_name.contact_owner IS NULL)";
@ -1009,9 +1020,62 @@ class Sql extends Api\Storage
|| strlen($contact['uid']) < $minimum_uid_length)) {
parent::update(array('uid' => Api\CalDAV::generate_uid('addressbook',$contact['id'])));
}
if (is_array($contact))
{
$contact['shared'] = $this->read_shared($contact['id']);
}
return $contact;
}
/**
* Read sharing information of a contact
*
* @param int $id contact_id to read
* @return array of array with values for keys "shared_(with|writable|by|at|id)"
*/
function read_shared($id)
{
$shared = [];
foreach($this->db->select(self::SHARED_TABLE, '*', ['contact_id' => $id],
__LINE__, __FILE__, false) as $row)
{
$row['shared_at'] = Api\DateTime::server2user($row['shared_at'], 'object');
$shared[] = $row;
}
return $shared;
}
/**
* @param int $id
* @param array $shared array of array with values for keys "shared_(with|writable|by|at|id)"
* @return array of array with values for keys "shared_(with|writable|by|at|id)"
*/
function save_shared($id, array $shared)
{
$ids = [];
foreach($shared as &$data)
{
if (empty($data['shared_id']))
{
unset($data['shared_id']);
$data['contact_id'] = $id;
$data['shared_at'] = Api\DateTime::user2server($data['shared_at'] ?: 'now');
$data['shared_by'] = $data['shared_by'] ?: $GLOBALS['egw_info']['user']['account_id'];
$this->db->insert(self::SHARED_TABLE, $data, false, __LINE__, __FILE__);
$data['shared_id'] = $this->db->get_last_insert_id(self::SHARED_TABLE, 'share_id');
}
$ids[] = (int)$data['shared_id'];
}
$delete = ['contact_id' => $id];
if ($ids) $delete[] = 'shared_id NOT IN ('.implode(',', $ids).')';
$this->db->delete(self::SHARED_TABLE, $delete, __LINE__, __FILE__);
foreach($shared as &$data)
{
$data['shared_at'] = Api\DateTime::server2user($data['shared_at'], 'object');
}
return $shared;
}
/**
* Saves a contact, reimplemented to check a given etag and set a uid
*
@ -1095,6 +1159,11 @@ class Sql extends Api\Storage
{
parent::update($update);
}
// save sharing information
if (!$err)
{
$this->data['shared'] = $this->save_shared($this->data['id'], (array)$this->data['shared']);
}
return $err;
}

View File

@ -608,7 +608,7 @@ class Storage
$contact_id = array('account_id' => (int) substr($contact_id,8));
}
// read main data
$backend =& $this->get_backend($contact_id);
$backend = $this->get_backend($contact_id);
if (!($contact = $backend->read($contact_id)))
{
return $contact;