Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F117880702
kolab_sync_data_contacts.php
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Authored By
Unknown
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
;
}
}
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
(
$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
Sun, Apr 5, 11:27 PM (1 w, 5 d ago)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
e9/bc/e19e8d8f16691e90f07fc7d2a03c
Default Alt Text
kolab_sync_data_contacts.php (21 KB)
Attached To
Mode
rS syncroton
Attached
Detach File
Event Timeline