diff --git a/addressbook/inc/class.addressbook_bo.inc.php b/addressbook/inc/class.addressbook_bo.inc.php
index f4cfb94bb7..277c995b52 100755
--- a/addressbook/inc/class.addressbook_bo.inc.php
+++ b/addressbook/inc/class.addressbook_bo.inc.php
@@ -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);
}
}
diff --git a/addressbook/inc/class.addressbook_ui.inc.php b/addressbook/inc/class.addressbook_ui.inc.php
index b9de816aba..d4a4c65a10 100644
--- a/addressbook/inc/class.addressbook_ui.inc.php
+++ b/addressbook/inc/class.addressbook_ui.inc.php
@@ -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']))
{
@@ -2350,7 +2355,7 @@ window.egw_LAB.wait(function() {
// Registry has view_id as contact_id, so set it (custom fields uses it)
$content['contact_id'] = $content['id'];
-
+
// Avoid ID conflict with tree & selectboxes
$content['cat_id_tree'] = $content['cat_id'];
@@ -2363,7 +2368,7 @@ window.egw_LAB.wait(function() {
$content['private_cfs']['#'.$name] = $content['#'.$name];
}
}
-
+
// how to display addresses
$content['addr_format'] = $this->addr_format_by_country($content['adr_one_countryname']);
$content['addr_format2'] = $this->addr_format_by_country($content['adr_two_countryname']);
@@ -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();
}
}
diff --git a/addressbook/inc/class.addressbook_vcal.inc.php b/addressbook/inc/class.addressbook_vcal.inc.php
index 3cca3a7ec1..f35928bfd1 100644
--- a/addressbook/inc/class.addressbook_vcal.inc.php
+++ b/addressbook/inc/class.addressbook_vcal.inc.php
@@ -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!
);
diff --git a/addressbook/templates/default/edit.xet b/addressbook/templates/default/edit.xet
index 2d65ca6d50..e52ed4dc0e 100644
--- a/addressbook/templates/default/edit.xet
+++ b/addressbook/templates/default/edit.xet
@@ -176,6 +176,14 @@
+
+
+
+
+
+
+
+
diff --git a/api/setup/setup.inc.php b/api/setup/setup.inc.php
index 7253acad68..f55a103a15 100644
--- a/api/setup/setup.inc.php
+++ b/api/setup/setup.inc.php
@@ -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';
-
-
diff --git a/api/setup/tables_current.inc.php b/api/setup/tables_current.inc.php
index fd8676afff..4353f837c8 100644
--- a/api/setup/tables_current.inc.php
+++ b/api/setup/tables_current.inc.php
@@ -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(),
diff --git a/api/setup/tables_update.inc.php b/api/setup/tables_update.inc.php
index 7525d85cea..8501d6bd97 100644
--- a/api/setup/tables_update.inc.php
+++ b/api/setup/tables_update.inc.php
@@ -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';
+}
diff --git a/api/src/Contacts.php b/api/src/Contacts.php
index d0d624517e..58587d212e 100755
--- a/api/src/Contacts.php
+++ b/api/src/Contacts.php
@@ -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))
{
diff --git a/api/src/Contacts/Sql.php b/api/src/Contacts/Sql.php
index 857cd71660..0ac5eb0d13 100644
--- a/api/src/Contacts/Sql.php
+++ b/api/src/Contacts/Sql.php
@@ -250,7 +250,7 @@ class Sql extends Api\Storage
* We join egw_addressbook to itself, and count how many fields match. If
* enough of the fields we care about match, we count those two records as
* duplicates.
- *
+ *
* @var array $param
* @var string $param[grouped_view] 'duplicate', 'duplicate,adr_one_location', 'duplicate,org_name' how to group
* @var int $param[owner] addressbook to search
@@ -368,7 +368,7 @@ class Sql extends Api\Storage
}
$query = $this->parse_search(array_merge($criteria, $filter), $wildcard, false, ' AND ');
- $sub_query = $this->db->select($this->table_name,
+ $sub_query = $this->db->select($this->table_name,
'DISTINCT ' . implode(', ',array_merge($columns, $extra)),
$query,
False, False, 0, $append, False, -1,
@@ -380,7 +380,7 @@ class Sql extends Api\Storage
{
$mysql_calc_rows = 'SQL_CALC_FOUND_ROWS ';
}
-
+
$rows = $this->db->query(
"SELECT $mysql_calc_rows " . $columns. ', COUNT(contact_id) AS group_count' .
' FROM (' . $sub_query . ') AS matches GROUP BY ' . implode(',',$group) .
@@ -396,7 +396,7 @@ class Sql extends Api\Storage
$row['email_home'] = $row['contact_email_home'];
$dupes[] = $this->db2data($row);
}
-
+
if ($mysql_calc_rows)
{
$this->total = $this->db->query('SELECT FOUND_ROWS()')->fetchColumn();
@@ -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);
diff --git a/api/src/Contacts/Storage.php b/api/src/Contacts/Storage.php
index 74a0dccc07..38053a63f8 100755
--- a/api/src/Contacts/Storage.php
+++ b/api/src/Contacts/Storage.php
@@ -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
*
@@ -816,7 +831,7 @@ class Storage
/**
* Find contacts that appear to be duplicates
- *
+ *
* @param Array $param
* @param string $param[org_view] 'org_name', 'org_name,adr_one_location', 'org_name,org_unit' how to group
* @param int $param[owner] addressbook to search
@@ -825,7 +840,7 @@ class Storage
* @param int $param[start]
* @param int $param[num_rows]
* @param string $param[sort] ASC or DESC
- *
+ *
* @return array of arrays
*/
public function duplicates($param)
@@ -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])
diff --git a/api/src/Etemplate/Widget/Vfs.php b/api/src/Etemplate/Widget/Vfs.php
index 12943f8504..f131785a0e 100644
--- a/api/src/Etemplate/Widget/Vfs.php
+++ b/api/src/Etemplate/Widget/Vfs.php
@@ -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)
{
diff --git a/api/src/Vfs.php b/api/src/Vfs.php
index 18d4c1e745..41e321b7da 100644
--- a/api/src/Vfs.php
+++ b/api/src/Vfs.php
@@ -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();