Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F1842101
kolab_sync_data_contacts.php
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
21 KB
Referenced Files
None
Subscribers
None
kolab_sync_data_contacts.php
View Options
<?php
/**
+--------------------------------------------------------------------------+
| Kolab Sync (ActiveSync for Kolab) |
| |
| Copyright (C) 2011-2017, Kolab Systems AG <contact@kolabsys.com> |
| |
| This program is free software: you can redistribute it and/or modify |
| it under the terms of the GNU Affero General Public License as published |
| by the Free Software Foundation, either version 3 of the License, or |
| (at your option) any later version. |
| |
| This program is distributed in the hope that it will be useful, |
| but WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| GNU Affero General Public License for more details. |
| |
| You should have received a copy of the GNU Affero General Public License |
| along with this program. If not, see <http://www.gnu.org/licenses/> |
+--------------------------------------------------------------------------+
| Author: Aleksander Machniak <machniak@kolabsys.com> |
+--------------------------------------------------------------------------+
*/
/**
* COntacts data class for Syncroton
*/
class
kolab_sync_data_contacts
extends
kolab_sync_data
{
/**
* Mapping from ActiveSync Contacts namespace fields
*/
protected
$mapping
=
array
(
'anniversary'
=>
'anniversary'
,
'assistantName'
=>
'assistant:0'
,
//'assistantPhoneNumber' => 'assistantphonenumber',
'birthday'
=>
'birthday'
,
'body'
=>
'notes'
,
'businessAddressCity'
=>
'address.work.locality'
,
'businessAddressCountry'
=>
'address.work.country'
,
'businessAddressPostalCode'
=>
'address.work.code'
,
'businessAddressState'
=>
'address.work.region'
,
'businessAddressStreet'
=>
'address.work.street'
,
'businessFaxNumber'
=>
'phone.workfax.number'
,
'businessPhoneNumber'
=>
'phone.work.number'
,
'carPhoneNumber'
=>
'phone.car.number'
,
//'categories' => 'categories',
'children'
=>
'children'
,
'companyName'
=>
'organization'
,
'department'
=>
'department'
,
//'email1Address' => 'email:0',
//'email2Address' => 'email:1',
//'email3Address' => 'email:2',
//'fileAs' => 'fileas', //@TODO: ?
'firstName'
=>
'firstname'
,
//'home2PhoneNumber' => 'home2phonenumber',
'homeAddressCity'
=>
'address.home.locality'
,
'homeAddressCountry'
=>
'address.home.country'
,
'homeAddressPostalCode'
=>
'address.home.code'
,
'homeAddressState'
=>
'address.home.region'
,
'homeAddressStreet'
=>
'address.home.street'
,
'homeFaxNumber'
=>
'phone.homefax.number'
,
'homePhoneNumber'
=>
'phone.home.number'
,
'jobTitle'
=>
'jobtitle'
,
'lastName'
=>
'surname'
,
'middleName'
=>
'middlename'
,
'mobilePhoneNumber'
=>
'phone.mobile.number'
,
//'officeLocation' => 'officelocation',
'otherAddressCity'
=>
'address.office.locality'
,
'otherAddressCountry'
=>
'address.office.country'
,
'otherAddressPostalCode'
=>
'address.office.code'
,
'otherAddressState'
=>
'address.office.region'
,
'otherAddressStreet'
=>
'address.office.street'
,
'pagerNumber'
=>
'phone.pager.number'
,
'picture'
=>
'photo'
,
//'radioPhoneNumber' => 'radiophonenumber',
//'rtf' => 'rtf',
'spouse'
=>
'spouse'
,
'suffix'
=>
'suffix'
,
'title'
=>
'prefix'
,
'webPage'
=>
'website.homepage.url'
,
//'yomiCompanyName' => 'yomicompanyname',
//'yomiFirstName' => 'yomifirstname',
//'yomiLastName' => 'yomilastname',
// Mapping from ActiveSync Contacts2 namespace fields
//'accountName' => 'accountname',
//'companyMainPhone' => 'companymainphone',
//'customerId' => 'customerid',
//'governmentId' => 'governmentid',
'iMAddress'
=>
'im:0'
,
'iMAddress2'
=>
'im:1'
,
'iMAddress3'
=>
'im:2'
,
'managerName'
=>
'manager:0'
,
//'mMS' => 'mms',
'nickName'
=>
'nickname'
,
);
/**
* Kolab object type
*
* @var string
*/
protected
$modelName
=
'contact'
;
/**
* Type of the default folder
*
* @var int
*/
protected
$defaultFolderType
=
Syncroton_Command_FolderSync
::
FOLDERTYPE_CONTACT
;
/**
* Default container for new entries
*
* @var string
*/
protected
$defaultFolder
=
'Contacts'
;
/**
* Type of user created folders
*
* @var int
*/
protected
$folderType
=
Syncroton_Command_FolderSync
::
FOLDERTYPE_CONTACT_USER_CREATED
;
/**
* Identifier of special Global Address List folder
*
* @var string
*/
protected
$galFolder
=
'GAL'
;
/**
* Name of special Global Address List folder
*
* @var string
*/
protected
$galFolderName
=
'Global Address Book'
;
protected
$galPrefix
=
'GAL:'
;
protected
$galSources
;
protected
$galResult
;
protected
$galCache
;
/**
* Creates model object
*
* @param Syncroton_Model_SyncCollection $collection Collection data
* @param string $serverId Local entry identifier
*/
public
function
getEntry
(
Syncroton_Model_SyncCollection
$collection
,
$serverId
)
{
$data
=
is_array
(
$serverId
)
?
$serverId
:
$this
->
getObject
(
$collection
->
collectionId
,
$serverId
);
$result
=
array
();
if
(
empty
(
$data
))
{
throw
new
Syncroton_Exception_NotFound
(
"Contact $serverId not found"
);
}
// Contacts namespace fields
foreach
(
$this
->
mapping
as
$key
=>
$name
)
{
$value
=
$this
->
getKolabDataItem
(
$data
,
$name
);
switch
(
$name
)
{
case
'photo'
:
if
(
$value
)
{
// ActiveSync limits photo size to 48KB (of base64 encoded string)
if
(
strlen
(
$value
)
*
1.33
>
48
*
1024
)
{
continue
2
;
}
}
break
;
case
'birthday'
:
case
'anniversary'
:
$value
=
self
::
date_from_kolab
(
$value
);
break
;
case
'notes'
:
$value
=
$this
->
body_from_kolab
(
$value
,
$collection
);
break
;
}
if
(
empty
(
$value
)
||
is_array
(
$value
))
{
continue
;
}
$result
[
$key
]
=
$value
;
}
// email address(es): email1Address, email2Address, email3Address
for
(
$x
=
0
;
$x
<
3
;
$x
++)
{
if
(!
empty
(
$data
[
'email'
][
$x
]))
{
$email
=
$data
[
'email'
][
$x
];
if
(
is_array
(
$email
))
{
$email
=
$email
[
'address'
];
}
if
(
$email
)
{
$result
[
'email'
.
(
$x
+
1
)
.
'Address'
]
=
$email
;
}
}
}
return
new
Syncroton_Model_Contact
(
$result
);
}
/**
* convert contact from xml to libkolab array
*
* @param Syncroton_Model_IEntry $data Contact to convert
* @param string $folderId Folder identifier
* @param array $entry Existing entry
*
* @return array Kolab object array
*/
public
function
toKolab
(
Syncroton_Model_IEntry
$data
,
$folderId
,
$entry
=
null
)
{
$contact
=
!
empty
(
$entry
)
?
$entry
:
array
();
// Contacts namespace fields
foreach
(
$this
->
mapping
as
$key
=>
$name
)
{
$value
=
$data
->
$key
;
switch
(
$name
)
{
case
'address.work.street'
:
if
(
strtolower
(
$this
->
device
->
devicetype
)
==
'palm'
)
{
// palm pre sends the whole address in the <Contacts:BusinessStreet> tag
$value
=
null
;
}
break
;
case
'website.homepage.url'
:
// remove facebook urls
if
(
preg_match
(
'/^fb:
\/\/
/'
,
$value
))
{
$value
=
null
;
}
break
;
case
'notes'
:
$value
=
$this
->
getBody
(
$value
,
Syncroton_Model_EmailBody
::
TYPE_PLAINTEXT
);
// If note isn't specified keep old note
if
(
$value
===
null
)
{
continue
2
;
}
break
;
case
'photo'
:
// If photo isn't specified keep old photo
if
(
$value
===
null
)
{
continue
2
;
}
break
;
case
'birthday'
:
case
'anniversary'
:
if
(
$value
)
{
// convert date to string format, so libkolab will store
// it with no time and timezone what could be incorrectly re-calculated (#2555)
$value
=
$value
->
format
(
'Y-m-d'
);
}
break
;
}
$this
->
setKolabDataItem
(
$contact
,
$name
,
$value
);
}
// email address(es): email1Address, email2Address, email3Address
$emails
=
array
();
for
(
$x
=
0
;
$x
<
3
;
$x
++)
{
$key
=
'email'
.
(
$x
+
1
)
.
'Address'
;
if
(
$value
=
$data
->
$key
)
{
// Android sends email address as: Lars Kneschke <l.kneschke@metaways.de>
if
(
preg_match
(
'/(.*)<(.+@[^@]+)>/'
,
$value
,
$matches
))
{
$value
=
trim
(
$matches
[
2
]);
}
// sanitize email address, it can contain broken (non-unicode) characters (#3287)
$value
=
rcube_charset
::
clean
(
$value
);
// try to find address type, at least we can do this if
// address wasn't changed
$type
=
''
;
foreach
((
array
)
$contact
[
'email'
]
as
$email
)
{
if
(
$email
[
'address'
]
==
$value
)
{
$type
=
$email
[
'type'
];
}
}
$emails
[]
=
array
(
'address'
=>
$value
,
'type'
=>
$type
);
}
}
$contact
[
'email'
]
=
$emails
;
return
$contact
;
}
/**
* Return list of supported folders for this backend
*
* @return array
*/
public
function
getAllFolders
()
{
$list
=
parent
::
getAllFolders
();
if
(
$this
->
isMultiFolder
()
&&
$this
->
hasGAL
())
{
$list
[
$this
->
galFolder
]
=
new
Syncroton_Model_Folder
(
array
(
'displayName'
=>
$this
->
galFolderName
,
// @TODO: localization?
'serverId'
=>
$this
->
galFolder
,
'parentId'
=>
0
,
'type'
=>
14
,
));
}
return
$list
;
}
/**
* Updates a folder
*/
public
function
updateFolder
(
Syncroton_Model_IFolder
$folder
)
{
if
(
$folder
->
serverId
===
$this
->
galFolder
&&
$this
->
hasGAL
())
{
throw
new
Syncroton_Exception_AccessDenied
(
"Updating GAL folder is not possible"
);
}
return
parent
::
updateFolder
(
$folder
);
}
/**
* Deletes a folder
*/
public
function
deleteFolder
(
$folder
)
{
if
(
$folder
instanceof
Syncroton_Model_IFolder
)
{
$folder
=
$folder
->
serverId
;
}
if
(
$folder
===
$this
->
galFolder
&&
$this
->
hasGAL
())
{
throw
new
Syncroton_Exception_AccessDenied
(
"Deleting GAL folder is not possible"
);
}
return
parent
::
deleteFolder
(
$folder
);
}
/**
* Empty folder (remove all entries and optionally subfolders)
*
* @param string $folderId Folder identifier
* @param array $options Options
*/
public
function
emptyFolderContents
(
$folderid
,
$options
)
{
if
(
$folderid
===
$this
->
galFolder
&&
$this
->
hasGAL
())
{
throw
new
Syncroton_Exception_AccessDenied
(
"Emptying GAL folder is not possible"
);
}
return
parent
::
emptyFolderContents
(
$folderid
,
$options
);
}
/**
* Moves object into another location (folder)
*
* @param string $srcFolderId Source folder identifier
* @param string $serverId Object identifier
* @param string $dstFolderId Destination folder identifier
*
* @throws Syncroton_Exception_Status
* @return string New object identifier
*/
public
function
moveItem
(
$srcFolderId
,
$serverId
,
$dstFolderId
)
{
if
(
strpos
(
$serverId
,
$this
->
galPrefix
)
===
0
&&
$this
->
hasGAL
())
{
throw
new
Syncroton_Exception_AccessDenied
(
"Moving GAL entries is not possible"
);
}
if
(
$srcFolderId
===
$this
->
galFolder
&&
$this
->
hasGAL
())
{
throw
new
Syncroton_Exception_AccessDenied
(
"Moving/Deleting GAL entries is not possible"
);
}
if
(
$dstFolderId
===
$this
->
galFolder
&&
$this
->
hasGAL
())
{
throw
new
Syncroton_Exception_AccessDenied
(
"Creating GAL entries is not possible"
);
}
return
parent
::
moveItem
(
$srcFolderId
,
$serverId
,
$dstFolderId
);
}
/**
* Add entry
*
* @param string $folderId Folder identifier
* @param Syncroton_Model_IEntry $entry Entry object
*
* @return string ID of the created entry
*/
public
function
createEntry
(
$folderId
,
Syncroton_Model_IEntry
$entry
)
{
if
(
$folderId
===
$this
->
galFolder
&&
$this
->
hasGAL
())
{
throw
new
Syncroton_Exception_AccessDenied
(
"Creating GAL entries is not possible"
);
}
return
parent
::
createEntry
(
$folderId
,
$entry
);
}
/**
* update existing entry
*
* @param string $folderId
* @param string $serverId
* @param SimpleXMLElement $entry
*
* @return string ID of the updated entry
*/
public
function
updateEntry
(
$folderId
,
$serverId
,
Syncroton_Model_IEntry
$entry
)
{
if
(
strpos
(
$serverId
,
$this
->
galPrefix
)
===
0
&&
$this
->
hasGAL
())
{
throw
new
Syncroton_Exception_AccessDenied
(
"Updating GAL entries is not possible"
);
}
return
parent
::
updateEntry
(
$folderId
,
$serverId
,
$entry
);
}
/**
* delete entry
*
* @param string $folderId
* @param string $serverId
* @param array $collectionData
*/
public
function
deleteEntry
(
$folderId
,
$serverId
,
$collectionData
)
{
if
(
strpos
(
$serverId
,
$this
->
galPrefix
)
===
0
&&
$this
->
hasGAL
())
{
throw
new
Syncroton_Exception_AccessDenied
(
"Deleting GAL entries is not possible"
);
}
return
parent
::
deleteEntry
(
$folderId
,
$serverId
,
$collectionData
);
}
/**
* Returns filter query array according to specified ActiveSync FilterType
*
* @param int $filter_type Filter type
*
* @param array Filter query
*/
protected
function
filter
(
$filter_type
=
0
)
{
// specify object type, contact folders in Kolab might
// contain also ditribution-list objects, we'll skip them
return
array
(
array
(
'type'
,
'='
,
$this
->
modelName
));
}
/**
* Check if GAL synchronization is enabled for current device
*/
protected
function
hasGAL
()
{
return
count
(
$this
->
getGALSources
());
}
/**
* Search for existing entries
*
* @param string $folderid Folder identifier
* @param array $filter Search filter
* @param int $result_type Type of the result (see RESULT_* constants)
*
* @return array|int Search result as count or array of uids/objects
*/
protected
function
searchEntries
(
$folderid
,
$filter
=
array
(),
$result_type
=
self
::
RESULT_UID
)
{
// GAL Folder exists, return result from LDAP only
if
(
$folderid
===
$this
->
galFolder
&&
$this
->
hasGAL
())
{
return
$this
->
searchGALEntries
(
$filter
,
$result_type
);
}
$result
=
parent
::
searchEntries
(
$folderid
,
$filter
,
$result_type
);
// Merge results from LDAP
if
(
$this
->
hasGAL
()
&&
!
$this
->
isMultiFolder
())
{
$gal_result
=
$this
->
searchGALEntries
(
$filter
,
$result_type
);
if
(
$result_type
==
self
::
RESULT_COUNT
)
{
$result
+=
$gal_result
;
}
else
{
$result
=
array_merge
(
$result
,
$gal_result
);
}
}
return
$result
;
}
/**
* Fetches the entry from the backend
*/
protected
function
getObject
(
$folderid
,
$entryid
,
&
$folder
=
null
)
{
if
(
strpos
(
$entryid
,
$this
->
galPrefix
)
===
0
&&
$this
->
hasGAL
())
{
return
$this
->
getGALEntry
(
$entryid
);
}
return
parent
::
getObject
(
$folderid
,
$entryid
,
$folder
);
}
/**
* Search for existing LDAP entries
*
* @param array $filter Search filter
* @param int $result_type Type of the result (see RESULT_* constants)
*
* @return array|int Search result as count or array of uids/objects
*/
protected
function
searchGALEntries
(
$filter
,
$result_type
)
{
// For GAL we don't check for changes.
// When something changed a new UID will be generated so the update
// will be done as delete + create
foreach
(
$filter
as
$f
)
{
if
(
$f
[
0
]
==
'changed'
)
{
return
$result_type
==
self
::
RESULT_COUNT
?
0
:
array
();
}
}
if
(
$this
->
galCache
&&
(
$result
=
$this
->
galCache
->
get
(
'index'
))
!==
null
)
{
$result
=
explode
(
"
\n
"
,
$result
);
return
$result_type
==
self
::
RESULT_COUNT
?
count
(
$result
)
:
$result
;
}
$result
=
array
();
foreach
(
$this
->
getGALSources
()
as
$source
)
{
if
(
$book
=
kolab_sync_data_gal
::
get_address_book
(
$source
[
'id'
]))
{
$book
->
reset
();
$book
->
set_page
(
1
);
$book
->
set_pagesize
(
10000
);
$set
=
$book
->
list_records
();
while
(
$contact
=
$set
->
next
())
{
$result
[]
=
$this
->
createGALEntryUID
(
$contact
,
$source
[
'id'
]);
}
}
}
if
(
$this
->
galCache
)
{
$this
->
galCache
->
set
(
'index'
,
implode
(
"
\n
"
,
$result
));
}
return
$result_type
==
self
::
RESULT_COUNT
?
count
(
$result
)
:
$result
;
}
/**
* Return specified LDAP entry
*
* @param string $serverId Entry identifier
*
* @return array Contact data
*/
protected
function
getGALEntry
(
$serverId
)
{
list
(
$source
,
$timestamp
,
$uid
)
=
$this
->
resolveGALEntryUID
(
$serverId
);
if
(
$source
&&
$uid
&&
(
$book
=
kolab_sync_data_gal
::
get_address_book
(
$source
)))
{
$book
->
reset
();
$set
=
$book
->
search
(
'uid'
,
array
(
$uid
),
rcube_addressbook
::
SEARCH_STRICT
,
true
,
true
);
$result
=
$set
->
first
();
if
(
$result
[
'uid'
]
==
$uid
&&
$result
[
'changed'
]
==
$timestamp
)
{
// As in kolab_sync_data_gal we use only one email address
if
(
empty
(
$result
[
'email'
]))
{
$emails
=
$book
->
get_col_values
(
'email'
,
$result
,
true
);
$result
[
'email'
]
=
array
(
$emails
[
0
]);
}
return
$result
;
}
}
}
/**
* Return LDAP address books list
*
* @return array Address books array
*/
protected
function
getGALSources
()
{
if
(
$this
->
galSources
===
null
)
{
$rcube
=
rcube
::
get_instance
();
$gal_sync
=
$rcube
->
config
->
get
(
'activesync_gal_sync'
);
$enabled
=
false
;
if
(
$gal_sync
===
true
)
{
$enabled
=
true
;
}
else
if
(
is_array
(
$gal_sync
))
{
$enabled
=
$this
->
deviceTypeFilter
(
$gal_sync
);
}
$this
->
galSources
=
$enabled
?
kolab_sync_data_gal
::
get_address_sources
()
:
array
();
if
(
$cache_type
=
$rcube
->
config
->
get
(
'activesync_gal_cache'
,
'db'
))
{
$cache_ttl
=
$rcube
->
config
->
get
(
'activesync_gal_cache_ttl'
,
'1d'
);
$this
->
galCache
=
$rcube
->
get_cache
(
'activesync_gal'
,
$cache_type
,
$cache_ttl
,
false
);
// expunge cache every now and then
if
(
rand
(
0
,
10
)
===
0
)
{
$this
->
galCache
->
expunge
();
}
}
}
return
$this
->
galSources
;
}
/**
* Builds contact identifier from contact data and source id
*/
protected
function
createGALEntryUID
(
$contact
,
$source_id
)
{
return
$this
->
galPrefix
.
sprintf
(
'%s:%s:%s'
,
rcube_ldap
::
dn_encode
(
$source_id
),
$contact
[
'changed'
],
$contact
[
'uid'
]);
}
/**
* Extracts contact identification data from contact identifier
*/
protected
function
resolveGALEntryUID
(
$uid
)
{
if
(
strpos
(
$uid
,
$this
->
galPrefix
)
===
0
)
{
$items
=
explode
(
':'
,
substr
(
$uid
,
strlen
(
$this
->
galPrefix
)));
$items
[
0
]
=
rcube_ldap
::
dn_decode
(
$items
[
0
]);
return
$items
;
// source, timestamp, uid
}
return
array
();
}
}
File Metadata
Details
Attached
Mime Type
text/x-php
Expires
Mon, Aug 25, 8:06 PM (1 d, 3 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
254497
Default Alt Text
kolab_sync_data_contacts.php (21 KB)
Attached To
Mode
R4 syncroton
Attached
Detach File
Event Timeline
Log In to Comment