* Addressbook: store S/Mime & PGP pubkey and photo (SQL backend only) in filesystem

This commit is contained in:
Ralf Becker 2017-09-19 11:38:02 +02:00
parent 23e654ab89
commit 48554590f4
12 changed files with 360 additions and 48 deletions

View File

@ -35,7 +35,7 @@ class addressbook_bo extends Api\Contacts
*/
public function get_pgp_keys($recipients)
{
return $this->get_keys($recipients, self::$pgp_key_regexp, '%-----BEGIN PGP PUBLIC KEY BLOCK-----%');
return $this->get_keys($recipients, true);
}
/**
@ -106,7 +106,7 @@ class addressbook_bo extends Api\Contacts
*/
public function ajax_set_pgp_keys($keys, $allow_user_updates=null)
{
$message = $this->set_keys($keys, self::$pgp_key_regexp, $allow_user_updates);
$message = $this->set_keys($keys, true, $allow_user_updates);
// add all keys to public keyserver too
$message .= "\n".lang('%1 key(s) added to public keyserver "%2".',
self::set_pgp_keyserver($keys), PARSE_URL(self::KEYSERVER_ADD, PHP_URL_HOST));
@ -154,16 +154,28 @@ class addressbook_bo extends Api\Contacts
return $added;
}
/**
* Where to store public key delpending on type and storage backend
*
* @param boolean $pgp true: PGP, false: S/Mime
* @param array $contact =null contact array to pass to get_backend()
* @return boolean true: store as file, false: store with contact
*/
public function pubkey_use_file($pgp, array $contact=null)
{
return $pgp || empty($contact) || get_class($this->get_backend($contact)) == 'EGroupware\\Api\\Contacts\\Sql';
}
/**
* Set keys for given email or account_id and key type based on regexp (SMIME or PGP), if user has necessary rights
*
* @param array $keys email|account_id => public key pairs to store
* @param string $key_regexp regular expresion for key type indication (SMIME|PGP)
* @param boolean $pgp true: PGP, false: S/Mime
* @param boolean $allow_user_updates = null for admins, set config to allow regular users to store their key
*
* @return string message of the update operation result
*/
public function set_keys ($keys, $key_regexp, $allow_user_updates = null)
public function set_keys ($keys, $pgp, $allow_user_updates = null)
{
if (isset($allow_user_updates) && isset($GLOBALS['egw_info']['user']['apps']['admin']))
{
@ -183,6 +195,15 @@ class addressbook_bo extends Api\Contacts
Config::save_value('own_account_acl', $this->own_account_acl, 'phpgwapi');
}
}
$key_regexp = $pgp ? self::$pgp_key_regexp : Api\Mail\Smime::$certificate_regexp;
$file = $pgp ? Api\Contacts::FILES_PGP_PUBKEY : Api\Contacts::FILES_SMIME_PUBKEY;
if (!preg_match($key_regexp, $key))
{
return lang('File is not a %1 public key!', $pgp ? lang('PGP') : lang('S/MIME'));
}
$criteria = array();
foreach($keys as $recipient => $key)
{
@ -210,7 +231,21 @@ class addressbook_bo extends Api\Contacts
{
$key = $keys[$contact['email']];
}
if (empty($contact['pubkey']) || !preg_match($key_regexp, $contact['pubkey']))
// key is stored in file for sql backend or allways for pgp key
$path = null;
if ($contact['id'] && $this->pubkey_use_file($pgp, $contact))
{
$path = Api\Link::vfs_path('addressbook', $contact['id'], $file);
$contact['contact_files'] |= $pgp ? self::FILES_BIT_PGP_PUBKEY : self::FILES_BIT_SMIME_PUBKEY;
// remove evtl. existing old pubkey
if (preg_match($key_regexp, $contact['pubkey']))
{
$contact['pubkey'] = preg_replace($key_regexp, '', $contact['pubkey']);
}
$updated++;
}
elseif (empty($contact['pubkey']) || !preg_match($key_regexp, $contact['pubkey']))
{
$contact['pubkey'] .= $key;
}
@ -220,7 +255,17 @@ class addressbook_bo extends Api\Contacts
}
if ($this->check_perms(Acl::EDIT, $contact) && $this->save($contact))
{
++$updated;
if ($path)
{
// check_perms && save check ACL, in case of access only via own-account we have to use root to allow the update
$backup = Api\Vfs::$is_root; Api\Vfs::$is_root = true;
if (file_put_contents($path, $key)) ++$updated;
Api\Vfs::$is_root = $backup;
}
else
{
++$updated;
}
}
}
if ($criteria == array('egw.addressbook.account_id' => array((int)$GLOBALS['egw_info']['user']['account_id'])))
@ -241,17 +286,27 @@ class addressbook_bo extends Api\Contacts
* EMail addresses are lowercased to make search case-insensitive
*
* @param string|int|array $recipients (array of) email addresses or numeric account-ids
* @param string $key_regexp
* @param string $criteria_filter
*
* @param boolean $pgp true: PGP, false: S/Mime public keys
* @return array email|account_id => key pairs
*/
public function get_keys ($recipients, $key_regexp, $criteria_filter)
protected function get_keys ($recipients, $pgp)
{
if (!$recipients) return array();
if (!is_array($recipients)) $recipients = array($recipients);
if ($pgp)
{
$key_regexp = self::$pgp_key_regexp;
$criteria_filter = '%-----BEGIN PGP PUBLIC KEY BLOCK-----%';
$file = Api\Contacts::FILES_PGP_PUBKEY;
}
else
{
$key_regexp = Api\Mail\Smime::$certificate_regexp;
$criteria_filter = '%-----BEGIN CERTIFICATE-----%';
$file = Api\Contacts::FILES_SMIME_PUBKEY;
}
$criteria = $result = array();
foreach($recipients as &$recipient)
{
@ -264,11 +319,14 @@ class addressbook_bo extends Api\Contacts
$criteria['contact_email'][] = $recipient = strtolower($recipient);
}
}
foreach($this->search($criteria, array('account_id', 'contact_email', 'contact_pubkey'), '', '', '', false, 'OR', false,
"contact_pubkey LIKE '". $criteria_filter ."'" ) as $contact)
foreach($this->search($criteria, array('account_id', 'contact_email', 'contact_pubkey'),
'', '', '', false, 'OR', false, null) as $contact)
{
$matches = null;
if (preg_match($key_regexp, $contact['pubkey'], $matches))
// first check for file and second for pubkey field (LDAP, AD or old SQL)
if (($content = @file_get_contents(vfs_path('addressbook', $contact['id'], $file))) &&
preg_match($key_regexp, $content, $matches) ||
preg_match($key_regexp, $contact['pubkey'], $matches))
{
$contact['email'] = strtolower($contact['email']);
if (empty($criteria['account_id']) || in_array($contact['email'], $recipients))
@ -294,7 +352,7 @@ class addressbook_bo extends Api\Contacts
*/
public function get_smime_keys($recipients)
{
return $this->get_keys($recipients, Api\Mail\Smime::$certificate_regexp, '%-----BEGIN CERTIFICATE-----%');
return $this->get_keys($recipients, false);
}
/**
@ -307,6 +365,6 @@ class addressbook_bo extends Api\Contacts
*/
public function set_smime_keys($keys, $allow_user_updates=null)
{
return $this->set_keys($keys, Api\Mail\Smime::$certificate_regexp, $allow_user_updates);
return $this->set_keys($keys, false, $allow_user_updates);
}
}

View File

@ -861,7 +861,7 @@ class addressbook_ui extends addressbook_bo
unset($query['col_filter']['org_name']);
unset($query['col_filter']['org_unit']);
unset($query['col_filter']['adr_one_locality']);
foreach(static::$duplicate_fields as $field => $label)
foreach(array_keys(static::$duplicate_fields) as $field)
{
unset($query['col_filter'][$field]);
}
@ -1429,7 +1429,7 @@ window.egw_LAB.wait(function() {
* Used for action on organisation and duplicate views
* @param string/int $action 'delete', 'vcard', 'csv' or nummerical account_id to move contacts to that addessbook
* @param array $checked contact id's to use if !$use_all
* @param boolean $use_all if true use all contacts of the current selection (in the session)
* @param boolean $use_all if true use all contacts of the current selection in the session (NOT used!)
* @param int &$success number of succeded actions
* @param int &$failed number of failed actions (not enought permissions)
* @param string &$action_msg translated verb for the actions, to be used in a message like %1 contacts 'deleted'
@ -1439,6 +1439,7 @@ window.egw_LAB.wait(function() {
*/
protected function find_grouped_ids($action,&$checked,$use_all,&$success,&$failed,&$action_msg,$session_name,&$msg)
{
unset($use_all);
$grouped_contacts = array();
foreach((array)$checked as $n => $id)
{
@ -2019,10 +2020,13 @@ window.egw_LAB.wait(function() {
// unset the duplicate_filed after submit because we don't need to warn user for second time about contact duplication
unset($content['presets_fields']);
}
$content['photo_unchanged'] = true; // hint no need to store photo
/* seems not to be used any more in favor or ajax_update_photo
if ($content['delete_photo'])
{
$content['jpegphoto'] = null;
unset($content['delete_photo']);
$content['photo_unchanged'] = false;
}
if (is_array($content['upload_photo']) && !empty($content['upload_photo']['tmp_name']) &&
$content['upload_photo']['tmp_name'] != 'none' &&
@ -2031,7 +2035,8 @@ window.egw_LAB.wait(function() {
$content['jpegphoto'] = $this->resize_photo($f);
fclose($f);
unset($content['upload_photo']);
}
$content['photo_unchanged'] = false;
}*/
$links = false;
if (!$content['id'] && is_array($content['link_to']['to_id']))
{
@ -3081,8 +3086,7 @@ window.egw_LAB.wait(function() {
}
/**
* Ajax method to update edited avatar photo via
* avatar widget.
* Ajax method to update edited avatar photo via avatar widget
*
* @param int $contact_id
* @param file string $file = null null means to delete
@ -3093,15 +3097,17 @@ window.egw_LAB.wait(function() {
$contact = $this->read($contact_id);
if ($file)
{
$filteredFile=substr($file, strpos($file, ",")+1);
$decoded = base64_decode($filteredFile);
$filteredFile = substr($file, strpos($file, ",")+1);
// resize photo if wider then default width of 240pixel (keeping aspect ratio)
$decoded = $this->resize_photo(base64_decode($filteredFile));
}
$contact ['jpegphoto'] = is_null($file)? $file: $decoded;
$contact['jpegphoto'] = is_null($file) ? $file : $decoded;
$contact['photo_unchanged'] = false; // hint photo is changed
$success = $this->save($contact);
if (!$success)
{
$response->alert($message);
$response->alert($this->error);
}
else
{
@ -3122,12 +3128,15 @@ window.egw_LAB.wait(function() {
{
$contact_id = $GLOBALS['egw']->accounts->id2name(substr($contact_id,8),'person_id');
}
if (!($contact = $this->read($contact_id)) || !$contact['jpegphoto'])
if (!($contact = $this->read($contact_id)) ||
empty($contact['jpegphoto']) && // LDAP/AD (not updated SQL)
!(($contact['files'] & Api\Contacts::FILES_BIT_PHOTO) && // new SQL in VFS
($size = filesize($url=Api\Link::vfs_path('addressbook', $contact_id, Api\Contacts::FILES_PHOTO)))))
{
Egw::redirect(Api\Image::find('addressbook','photo'));
}
// use an etag over the image mapp
$etag = '"'.$contact['id'].':'.$contact['etag'].'"';
$etag = '"'.$contact_id.':'.$contact['etag'].'"';
if (!ob_get_contents())
{
header('Content-type: image/jpeg');
@ -3143,11 +3152,16 @@ window.egw_LAB.wait(function() {
{
header("HTTP/1.1 304 Not Modified");
}
else
elseif(!empty($contact['jpegphoto']))
{
header('Content-length: '.bytes($contact['jpegphoto']));
echo $contact['jpegphoto'];
}
else
{
header('Content-length: '.$size);
readfile($url);
}
exit();
}
}

View File

@ -72,6 +72,7 @@ class addressbook_vcal extends Api\Contacts
'X-ASSISTANT-TEL' => array('tel_assistent'),
'UID' => array('uid'),
'REV' => array('modified'),
//'KEY' multivalued with mime-type to export PGP and S/Mime public keys
//set for Apple: 'X-ABSHOWAS' => array('fileas_type'), // Horde vCard class uses uppercase prop-names!
);

View File

@ -176,6 +176,14 @@
<description value="Next date"/>
<link id="next_link"/>
</row>
<row valign="top">
<description value="SMIME key"/>
<vfs-upload id="addressbook:$cont[id]:.files/smime-pubkey.crt" accept=".crt,.pem,application/x-x509-ca-cert,application/x-x509-user-cert" mime="/application\/x-x509-(ca|user)-cert/"/>
</row>
<row valign="top">
<description value="PGP key"/>
<vfs-upload id="addressbook:$cont[id]:.files/pgp-pubkey.asc" accept=".asc,application/pgp-keys" mime="application/pgp-keys"/>
</row>
<row valign="top">
<description for="pubkey" value="Public key"/>
<textbox multiline="true" id="pubkey" rows="4" resize_ratio="0" class="et2_fullWidth"/>

View File

@ -12,7 +12,7 @@
/* Basic information about this app */
$setup_info['api']['name'] = 'api';
$setup_info['api']['title'] = 'EGroupware API';
$setup_info['api']['version'] = '16.9.002';
$setup_info['api']['version'] = '16.9.004';
$setup_info['api']['versions']['current_header'] = '1.29';
// maintenance release in sync with changelog in doc/rpm-build/debian.changes
$setup_info['api']['versions']['maintenance_release'] = $setup_info['api']['version'];
@ -130,5 +130,3 @@ $setup_info['groupdav']['author'] = $setup_info['groupdav']['maintainer'] = arra
$setup_info['groupdav']['license'] = 'GPL';
$setup_info['groupdav']['hooks']['preferences'] = 'EGroupware\\Api\\CalDAV\\Hooks::menus';
$setup_info['groupdav']['hooks']['settings'] = 'EGroupware\\Api\\CalDAV\\Hooks::settings';

View File

@ -253,13 +253,13 @@ $phpgw_baseline = array(
'contact_creator' => array('type' => 'int','meta' => 'user','precision' => '4','nullable' => False,'comment' => 'account id of the creator'),
'contact_modified' => array('type' => 'int','meta' => 'timestamp','precision' => '8','nullable' => False,'comment' => 'timestamp of the last modified'),
'contact_modifier' => array('type' => 'int','meta' => 'user','precision' => '4','comment' => 'account id of the last modified'),
'contact_jpegphoto' => array('type' => 'blob','comment' => 'photo of the contact (attachment)'),
'account_id' => array('type' => 'int','meta' => 'user','precision' => '4','comment' => 'account id'),
'contact_etag' => array('type' => 'int','precision' => '4','default' => '0','comment' => 'etag of the changes'),
'contact_uid' => array('type' => 'ascii','precision' => '128','comment' => 'unique id of the contact'),
'adr_one_countrycode' => array('type' => 'ascii','precision' => '2','comment' => 'countrycode (business)'),
'adr_two_countrycode' => array('type' => 'ascii','precision' => '2','comment' => 'countrycode (private)'),
'carddav_name' => array('type' => 'ascii','precision' => '128','comment' => 'name part of CardDAV URL, if specified by client')
'carddav_name' => array('type' => 'ascii','precision' => '128','comment' => 'name part of CardDAV URL, if specified by client'),
'contact_files' => array('type' => 'int','precision' => '1','default' => '0','comment' => '&1: photo, &2: pgp, &4: smime')
),
'pk' => array('contact_id'),
'fk' => array(),

View File

@ -235,3 +235,164 @@ function api_upgrade16_9_001()
return $GLOBALS['setup_info']['api']['currentver'] = '16.9.002';
}
/**
* Add contact_files bit-field and strip jpeg photo, PGP & S/Mime pubkeys from table
*
* @return string
*/
function api_upgrade16_9_002()
{
$GLOBALS['egw_setup']->oProc->AddColumn('egw_addressbook','contact_files',array(
'type' => 'int',
'precision' => '1',
'default' => '0',
'comment' => '&1: photo, &2: pgp, &4: smime'
));
$junk_size = 100;
$total = 0;
Api\Vfs::$is_root = true;
do {
$n = 0;
foreach($GLOBALS['egw_setup']->db->query("SELECT contact_id,contact_jpegphoto,contact_pubkey
FROM egw_addressbook
WHERE contact_jpegphoto IS NOT NULL OR contact_pubkey IS NOT NULL AND contact_pubkey LIKE '%-----%'",
__LINE__, __FILE__, 0, $junk_size, false, Api\Db::FETCH_ASSOC) as $row)
{
$row['contact_files'] = 0;
$contact_id = $row['contact_id'];
unset($row['contact_id']);
if ($row['contact_jpegphoto'] && ($fp = Api\Vfs::string_stream($row['contact_jpegphoto'])))
{
if (Api\Link::attach_file('addressbook', $contact_id, array(
'name' => Api\Contacts::FILES_PHOTO,
'type' => 'image/jpeg',
'tmp_name' => $fp,
)))
{
$row['contact_files'] |= Api\Contacts::FILES_BIT_PHOTO;
$row['contact_jpegphoto'] = null;
}
fclose($fp);
}
foreach(array(
array(addressbook_bo::$pgp_key_regexp, Api\Contacts::FILES_PGP_PUBKEY, Api\Contacts::FILES_BIT_PGP_PUBKEY, 'application/pgp-keys'),
array(Api\Mail\Smime::$pubkey_regexp, Api\Contacts::FILES_SMIME_PUBKEY, Api\Contacts::FILES_BIT_SMIME_PUBKEY, 'application/x-pem-file'),
) as $data)
{
list($regexp, $file, $bit, $mime) = $data;
$matches = null;
if ($row['contact_pubkey'] && preg_match($regexp, $row['contact_pubkey'], $matches) &&
($fp = Api\Vfs::string_stream($matches[0])))
{
if (Api\Link::attach_file('addressbook', $contact_id, array(
'name' => $file,
'type' => $mime,
'tmp_name' => $fp,
)))
{
$row['contact_files'] |= $bit;
$row['contact_pubkey'] = str_replace($matches[0], '', $row['contact_pubkey']);
}
fclose($fp);
}
}
if (!trim($row['contact_pubkey'])) $row['contact_pubkey'] = null;
if ($row['contact_files'])
{
$GLOBALS['egw_setup']->db->update('egw_addressbook', $row, array('contact_id' => $contact_id), __LINE__, __FILE__);
$total++;
}
$n++;
}
}
while($n == $junk_size);
Api\Vfs::$is_root = false;
return $GLOBALS['setup_info']['api']['currentver'] = '16.9.003';
}
/**
* Drop contact_jpegphoto column
*
* @return string
*/
function api_upgrade16_9_003()
{
$GLOBALS['egw_setup']->oProc->DropColumn('egw_addressbook',array(
'fd' => array(
'contact_id' => array('type' => 'auto','nullable' => False),
'contact_tid' => array('type' => 'char','precision' => '1','default' => 'n'),
'contact_owner' => array('type' => 'int','meta' => 'account','precision' => '8','nullable' => False,'comment' => 'account or group id of the adressbook'),
'contact_private' => array('type' => 'int','precision' => '1','default' => '0','comment' => 'privat or personal'),
'cat_id' => array('type' => 'ascii','meta' => 'category','precision' => '255','comment' => 'Category(s)'),
'n_family' => array('type' => 'varchar','precision' => '64','comment' => 'Family name'),
'n_given' => array('type' => 'varchar','precision' => '64','comment' => 'Given Name'),
'n_middle' => array('type' => 'varchar','precision' => '64'),
'n_prefix' => array('type' => 'varchar','precision' => '64','comment' => 'Prefix'),
'n_suffix' => array('type' => 'varchar','precision' => '64','comment' => 'Suffix'),
'n_fn' => array('type' => 'varchar','precision' => '128','comment' => 'Full name'),
'n_fileas' => array('type' => 'varchar','precision' => '255','comment' => 'sort as'),
'contact_bday' => array('type' => 'varchar','precision' => '12','comment' => 'Birtday'),
'org_name' => array('type' => 'varchar','precision' => '128','comment' => 'Organisation'),
'org_unit' => array('type' => 'varchar','precision' => '64','comment' => 'Department'),
'contact_title' => array('type' => 'varchar','precision' => '64','comment' => 'jobtittle'),
'contact_role' => array('type' => 'varchar','precision' => '64','comment' => 'role'),
'contact_assistent' => array('type' => 'varchar','precision' => '64','comment' => 'Name of the Assistent (for phone number)'),
'contact_room' => array('type' => 'varchar','precision' => '64','comment' => 'room'),
'adr_one_street' => array('type' => 'varchar','precision' => '64','comment' => 'street (business)'),
'adr_one_street2' => array('type' => 'varchar','precision' => '64','comment' => 'street (business) - 2. line'),
'adr_one_locality' => array('type' => 'varchar','precision' => '64','comment' => 'city (business)'),
'adr_one_region' => array('type' => 'varchar','precision' => '64','comment' => 'region (business)'),
'adr_one_postalcode' => array('type' => 'varchar','precision' => '64','comment' => 'postalcode (business)'),
'adr_one_countryname' => array('type' => 'varchar','precision' => '64','comment' => 'countryname (business)'),
'contact_label' => array('type' => 'text','comment' => 'currently not used'),
'adr_two_street' => array('type' => 'varchar','precision' => '64','comment' => 'street (private)'),
'adr_two_street2' => array('type' => 'varchar','precision' => '64','comment' => 'street (private) - 2. line'),
'adr_two_locality' => array('type' => 'varchar','precision' => '64','comment' => 'city (private)'),
'adr_two_region' => array('type' => 'varchar','precision' => '64','comment' => 'region (private)'),
'adr_two_postalcode' => array('type' => 'varchar','precision' => '64','comment' => 'postalcode (private)'),
'adr_two_countryname' => array('type' => 'varchar','precision' => '64','comment' => 'countryname (private)'),
'tel_work' => array('type' => 'varchar','precision' => '40','comment' => 'phone-number (business)'),
'tel_cell' => array('type' => 'varchar','precision' => '40','comment' => 'mobil phone (business)'),
'tel_fax' => array('type' => 'varchar','precision' => '40','comment' => 'fax-number (business)'),
'tel_assistent' => array('type' => 'varchar','precision' => '40','comment' => 'phone-number assistent'),
'tel_car' => array('type' => 'varchar','precision' => '40'),
'tel_pager' => array('type' => 'varchar','precision' => '40','comment' => 'pager'),
'tel_home' => array('type' => 'varchar','precision' => '40','comment' => 'phone-number (private)'),
'tel_fax_home' => array('type' => 'varchar','precision' => '40','comment' => 'fax-number (private)'),
'tel_cell_private' => array('type' => 'varchar','precision' => '40','comment' => 'mobil phone (private)'),
'tel_other' => array('type' => 'varchar','precision' => '40','comment' => 'other phone'),
'tel_prefer' => array('type' => 'varchar','precision' => '32','comment' => 'prefered phone-number'),
'contact_email' => array('type' => 'varchar','precision' => '128','comment' => 'email address (business)'),
'contact_email_home' => array('type' => 'varchar','precision' => '128','comment' => 'email address (private)'),
'contact_url' => array('type' => 'varchar','precision' => '128','comment' => 'website (business)'),
'contact_url_home' => array('type' => 'varchar','precision' => '128','comment' => 'website (private)'),
'contact_freebusy_uri' => array('type' => 'ascii','precision' => '128','comment' => 'freebusy-url for calendar of the contact'),
'contact_calendar_uri' => array('type' => 'ascii','precision' => '128','comment' => 'url for users calendar - currently not used'),
'contact_note' => array('type' => 'varchar','precision' => '8192','comment' => 'notes field'),
'contact_tz' => array('type' => 'varchar','precision' => '8','comment' => 'timezone difference'),
'contact_geo' => array('type' => 'ascii','precision' => '32','comment' => 'currently not used'),
'contact_pubkey' => array('type' => 'ascii','precision' => '16384','comment' => 'public key'),
'contact_created' => array('type' => 'int','meta' => 'timestamp','precision' => '8','comment' => 'timestamp of the creation'),
'contact_creator' => array('type' => 'int','meta' => 'user','precision' => '4','nullable' => False,'comment' => 'account id of the creator'),
'contact_modified' => array('type' => 'int','meta' => 'timestamp','precision' => '8','nullable' => False,'comment' => 'timestamp of the last modified'),
'contact_modifier' => array('type' => 'int','meta' => 'user','precision' => '4','comment' => 'account id of the last modified'),
'account_id' => array('type' => 'int','meta' => 'user','precision' => '4','comment' => 'account id'),
'contact_etag' => array('type' => 'int','precision' => '4','default' => '0','comment' => 'etag of the changes'),
'contact_uid' => array('type' => 'ascii','precision' => '128','comment' => 'unique id of the contact'),
'adr_one_countrycode' => array('type' => 'ascii','precision' => '2','comment' => 'countrycode (business)'),
'adr_two_countrycode' => array('type' => 'ascii','precision' => '2','comment' => 'countrycode (private)'),
'carddav_name' => array('type' => 'ascii','precision' => '128','comment' => 'name part of CardDAV URL, if specified by client'),
'contact_files' => array('type' => 'int','precision' => '1','default' => '0','comment' => '&1: photo, &2: pgp, &4: smime')
),
'pk' => array('contact_id'),
'fk' => array(),
'ix' => array('contact_owner','cat_id','n_fileas','contact_modified','contact_uid','carddav_name',array('n_family','n_given'),array('n_given','n_family'),array('org_name','n_family','n_given')),
'uc' => array('account_id')
),'contact_jpegphoto');
return $GLOBALS['setup_info']['api']['currentver'] = '16.9.004';
}

View File

@ -734,7 +734,7 @@ class Contacts extends Contacts\Storage
$data[$name] = DateTime::server2user($data[$name], $date_format);
}
}
$data['photo'] = $this->photo_src($data['id'],$data['jpegphoto'] || $data['files'] & self::FILES_PHOTO,'',$data['etag']);
$data['photo'] = $this->photo_src($data['id'],$data['jpegphoto'] || ($data['files'] & self::FILES_BIT_PHOTO), '', $data['etag']);
// set freebusy_uri for accounts
if (!$data['freebusy_uri'] && !$data['owner'] && $data['account_id'] && !is_object($GLOBALS['egw_setup']))
@ -1054,13 +1054,15 @@ class Contacts extends Contacts\Storage
}
/**
* Resizes photo to 60*80 pixel and returns it
* Resize photo down to 240pixel width and returns it
*
* Also makes sures photo is a JPEG.
*
* @param string|FILE $photo string with image or open filedescribtor
* @param int $dst_w =240 max width to resize to
* @return string with resized jpeg photo, null on error
*/
public static function resize_photo($photo,$dst_w=240)
public static function resize_photo($photo, $dst_w=240)
{
if (is_resource($photo))
{

View File

@ -1022,6 +1022,30 @@ class Sql extends Api\Storage
{
$update['carddav_name'] = $this->data['id'].'.vcf';
}
// update photo in entry-directory, unless hinted it is unchanged
if (!$err && $this->data['photo_unchanged'] !== true)
{
// in case files bit-field is not available read it from DB
if (!isset($this->data['files']))
{
$this->data['files'] = (int)$this->db->select($this->table_name, 'contact_files', array(
'contact_id' => $this->data['id'],
), __LINE__, __FILE__)->fetchColumn();
}
$path = Api\Link::vfs_path('addressbook', $this->data['id'], Api\Contacts::FILES_PHOTO);
$backup = Api\Vfs::$is_root; Api\Vfs::$is_root = true;
if (empty($this->data['jpegphoto']))
{
unlink($path);
$update['files'] = $this->data['files'] & ~Api\Contacts::FILES_BIT_PHOTO;
}
else
{
file_put_contents($path, $this->data['jpegphoto']);
$update['files'] = $this->data['files'] | Api\Contacts::FILES_BIT_PHOTO;
}
Api\Vfs::$is_root = $backup;
}
if (!$err && $update)
{
parent::update($update);

View File

@ -159,6 +159,21 @@ class Storage
*/
var $content_types = array();
/**
* Directory to store striped photo or public keys in VFS directory of entry
*/
const FILES_DIRECTORY = '.files';
const FILES_PHOTO = '.files/photo.jpeg';
const FILES_PGP_PUBKEY = '.files/pgp-pubkey.asc';
const FILES_SMIME_PUBKEY = '.files/smime-pubkey.crt';
/**
* Constant for bit-field "contact_files" storing what files are available
*/
const FILES_BIT_PHOTO = 1;
const FILES_BIT_PGP_PUBKEY = 2;
const FILES_BIT_SMIME_PUBKEY = 4;
/**
* These fields are options for checking for duplicate contacts
*
@ -886,7 +901,7 @@ class Storage
foreach($rows as $n => $row)
{
$rows[$n]['id'] = 'duplicate:';
foreach(static::$duplicate_fields as $by => $by_label)
foreach(array_keys(static::$duplicate_fields) as $by)
{
if (strpos($row[$by],'&')!==false) $row[$by] = str_replace('&','*AND*',$row[$by]);
if($row[$by])

View File

@ -84,10 +84,10 @@ class Vfs extends File
// Single file, missing extension in path
else if (substr($path, -1) != '/' && !Api\Vfs::file_exists($path) && $relpath && substr($relpath,-4,1) !== '.')
{
$find = Api\Vfs::find(substr($path,0, - strlen($relpath)), array(
$find = Api\Vfs::find(Api\Vfs::dirname($path), array(
'type' => 'f',
'maxdepth' => 1,
'name' => $relpath . '*'
'name' => Api\Vfs::basename($path).'.*',
));
foreach($find as $file)
{
@ -264,11 +264,14 @@ class Vfs extends File
{
// add extension to path
$parts = explode('.',$filename);
if (($extension = array_pop($parts)) && Api\MimeMagic::ext2mime($extension)) // really an extension --> add it to path
// check if path already contains a valid extension --> dont add an other one
$path_parts = explode('.', $path);
if (count($path_parts) > 2 && (!($extension = array_pop($path_parts)) || !Api\MimeMagic::ext2mime($extension)) &&
($extension = array_pop($parts)) && Api\MimeMagic::ext2mime($extension)) // really an extension --> add it to path
{
$path .= '.'.$extension;
$file['name'] = Api\Vfs::basename($path);
}
$file['name'] = Api\Vfs::basename($path);
}
else if ($path) // multiple upload with dir given (trailing slash)
{

View File

@ -2534,6 +2534,34 @@ class Vfs
{
return Vfs\StreamWrapper::load_wrapper($scheme);
}
/**
* Return stream with given string as content
*
* @param string $string
* @return boolean|resource stream or false on error
*/
static function string_stream($string)
{
if (!($fp = fopen('php://temp', 'rw')))
{
return false;
}
$pos = 0;
$len = strlen($string);
do {
if (!($written = fwrite($fp, substr($string, $pos))))
{
return false;
}
$pos += $written;
}
while ($len < $pos);
rewind($fp);
return $fp;
}
}
Vfs::init_static();