Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F117881480
kolab_sync_backend.php
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Authored By
Unknown
Size
32 KB
Referenced Files
None
Subscribers
None
kolab_sync_backend.php
View Options
<?php
/**
+--------------------------------------------------------------------------+
| Kolab Sync (ActiveSync for Kolab) |
| |
| Copyright (C) 2011-2012, 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> |
+--------------------------------------------------------------------------+
*/
class
kolab_sync_backend
{
/**
* Singleton instace of kolab_sync_backend
*
* @var kolab_sync_backend
*/
static
protected
$instance
;
protected
$storage
;
protected
$folder_meta
;
protected
$folder_uids
;
protected
$root_meta
;
static
protected
$types
=
array
(
1
=>
''
,
2
=>
'mail.inbox'
,
3
=>
'mail.drafts'
,
4
=>
'mail.wastebasket'
,
5
=>
'mail.sentitems'
,
6
=>
'mail.outbox'
,
7
=>
'task.default'
,
8
=>
'event.default'
,
9
=>
'contact.default'
,
10
=>
'note.default'
,
11
=>
'journal.default'
,
12
=>
'mail'
,
13
=>
'event'
,
14
=>
'contact'
,
15
=>
'task'
,
16
=>
'journal'
,
17
=>
'note'
,
);
static
protected
$classes
=
array
(
Syncroton_Data_Factory
::
CLASS_CALENDAR
=>
'event'
,
Syncroton_Data_Factory
::
CLASS_CONTACTS
=>
'contact'
,
Syncroton_Data_Factory
::
CLASS_EMAIL
=>
'mail'
,
Syncroton_Data_Factory
::
CLASS_NOTES
=>
'note'
,
Syncroton_Data_Factory
::
CLASS_TASKS
=>
'task'
,
);
const
ROOT_MAILBOX
=
'INBOX'
;
// const ROOT_MAILBOX = '';
const
ASYNC_KEY
=
'/private/vendor/kolab/activesync'
;
const
UID_KEY
=
'/shared/vendor/cmu/cyrus-imapd/uniqueid'
;
/**
* This implements the 'singleton' design pattern
*
* @return kolab_sync_backend The one and only instance
*/
static
function
get_instance
()
{
if
(!
self
::
$instance
)
{
self
::
$instance
=
new
kolab_sync_backend
;
self
::
$instance
->
startup
();
// init AFTER object was linked with self::$instance
}
return
self
::
$instance
;
}
/**
* Class initialization
*/
public
function
startup
()
{
$this
->
storage
=
rcube
::
get_instance
()->
get_storage
();
// @TODO: reset cache? if we do this for every request the cache would be useless
// There's no session here
//$this->storage->clear_cache('mailboxes.', true);
// set additional header used by libkolab
$this
->
storage
->
set_options
(
array
(
// @TODO: there can be Roundcube plugins defining additional headers,
// we maybe would need to add them here
'fetch_headers'
=>
'X-KOLAB-TYPE X-KOLAB-MIME-VERSION'
,
'skip_deleted'
=>
true
,
'threading'
=>
false
,
));
// Disable paging
$this
->
storage
->
set_pagesize
(
999999
);
}
/**
* List known devices
*
* @return array Device list as hash array
*/
public
function
devices_list
()
{
if
(
$this
->
root_meta
===
null
)
{
// @TODO: consider server annotation instead of INBOX
if
(
$meta
=
$this
->
storage
->
get_metadata
(
self
::
ROOT_MAILBOX
,
self
::
ASYNC_KEY
))
{
$this
->
root_meta
=
$this
->
unserialize_metadata
(
$meta
[
self
::
ROOT_MAILBOX
][
self
::
ASYNC_KEY
]);
}
else
{
$this
->
root_meta
=
array
();
}
}
if
(!
empty
(
$this
->
root_meta
[
'DEVICE'
])
&&
is_array
(
$this
->
root_meta
[
'DEVICE'
]))
{
return
$this
->
root_meta
[
'DEVICE'
];
}
return
array
();
}
/**
* Get list of folders available for sync
*
* @param string $deviceid Device identifier
* @param string $type Folder type
* @param bool $flat_mode Enables flat-list mode
*
* @return array|bool List of mailbox folders, False on backend failure
*/
public
function
folders_list
(
$deviceid
,
$type
,
$flat_mode
=
false
)
{
// get all folders of specified type
$folders
=
kolab_storage
::
list_folders
(
''
,
'*'
,
$type
,
false
,
$typedata
);
// get folders activesync config
$folderdata
=
$this
->
folder_meta
();
if
(!
is_array
(
$folders
)
||
!
is_array
(
$folderdata
))
{
return
false
;
}
$folders_list
=
array
();
// check if folders are "subscribed" for activesync
foreach
(
$folderdata
as
$folder
=>
$meta
)
{
if
(
empty
(
$meta
[
'FOLDER'
])
||
empty
(
$meta
[
'FOLDER'
][
$deviceid
])
||
empty
(
$meta
[
'FOLDER'
][
$deviceid
][
'S'
])
)
{
continue
;
}
// force numeric folder name to be a string (T1283)
$folder
=
(
string
)
$folder
;
if
(!
empty
(
$type
)
&&
!
in_array
(
$folder
,
$folders
))
{
continue
;
}
// Activesync folder identifier (serverId)
$folder_type
=
$typedata
[
$folder
]
?:
'mail'
;
$folder_id
=
self
::
folder_id
(
$folder
,
$folder_type
);
$folders_list
[
$folder_id
]
=
$this
->
folder_data
(
$folder
,
$folder_type
);
}
if
(
$flat_mode
)
{
$folders_list
=
$this
->
folders_list_flat
(
$folders_list
,
$type
,
$typedata
);
}
return
$folders_list
;
}
/**
* Converts list of folders to a "flat" list
*/
private
function
folders_list_flat
(
$folders
,
$type
,
$typedata
)
{
$delim
=
$this
->
storage
->
get_hierarchy_delimiter
();
foreach
(
$folders
as
$idx
=>
$folder
)
{
if
(
$folder
[
'parentId'
])
{
// for non-mail folders we make the list completely flat
if
(
$type
!=
'mail'
)
{
$display_name
=
kolab_storage
::
object_name
(
$folder
[
'imap_name'
]);
$display_name
=
html_entity_decode
(
$display_name
,
ENT_COMPAT
,
RCUBE_CHARSET
);
$folders
[
$idx
][
'parentId'
]
=
0
;
$folders
[
$idx
][
'displayName'
]
=
$display_name
;
}
// for mail folders we modify only folders with non-existing parents
else
if
(!
isset
(
$folders
[
$folder
[
'parentId'
]]))
{
$items
=
explode
(
$delim
,
$folder
[
'imap_name'
]);
$parent
=
0
;
// find existing parent
while
(
count
(
$items
)
>
0
)
{
array_pop
(
$items
);
$parent_name
=
implode
(
$delim
,
$items
);
$parent_type
=
$typedata
[
$parent_name
]
?:
'mail'
;
$parent_id
=
self
::
folder_id
(
$parent_name
,
$parent_type
);
if
(
isset
(
$folders
[
$parent_id
]))
{
$parent
=
$parent_id
;
break
;
}
}
if
(!
$parent
)
{
$display_name
=
kolab_storage
::
object_name
(
$folder
[
'imap_name'
]);
$display_name
=
html_entity_decode
(
$display_name
,
ENT_COMPAT
,
RCUBE_CHARSET
);
}
else
{
$parent_name
=
$folders
[
$parent_id
][
'imap_name'
];
$display_name
=
substr
(
$folder
[
'imap_name'
],
strlen
(
$parent_name
)+
1
);
$display_name
=
rcube_charset
::
convert
(
$display_name
,
'UTF7-IMAP'
);
$display_name
=
str_replace
(
$delim
,
' » '
,
$display_name
);
}
$folders
[
$idx
][
'parentId'
]
=
$parent
;
$folders
[
$idx
][
'displayName'
]
=
$display_name
;
}
}
}
return
$folders
;
}
/**
* Getter for folder metadata
*
* @return array|bool Hash array with meta data for each folder, False on backend failure
*/
public
function
folder_meta
()
{
if
(!
isset
(
$this
->
folder_meta
))
{
// get folders activesync config
$folderdata
=
$this
->
storage
->
get_metadata
(
"*"
,
self
::
ASYNC_KEY
);
if
(!
is_array
(
$folderdata
))
{
return
$this
->
folder_meta
=
false
;
}
$this
->
folder_meta
=
array
();
foreach
(
$folderdata
as
$folder
=>
$meta
)
{
if
(
$asyncdata
=
$meta
[
self
::
ASYNC_KEY
])
{
if
(
$metadata
=
$this
->
unserialize_metadata
(
$asyncdata
))
{
$this
->
folder_meta
[
$folder
]
=
$metadata
;
}
}
}
}
return
$this
->
folder_meta
;
}
/**
* Creates folder and subscribes to the device
*
* @param string $name Folder name (UTF7-IMAP)
* @param int $type Folder (ActiveSync) type
* @param string $deviceid Device identifier
*
* @return bool True on success, False on failure
*/
public
function
folder_create
(
$name
,
$type
,
$deviceid
)
{
if
(
$this
->
storage
->
folder_exists
(
$name
))
{
$created
=
true
;
}
else
{
$type
=
self
::
type_activesync2kolab
(
$type
);
$created
=
kolab_storage
::
folder_create
(
$name
,
$type
,
true
);
}
if
(
$created
)
{
// Set ActiveSync subscription flag
$this
->
folder_set
(
$name
,
$deviceid
,
1
);
return
true
;
}
return
false
;
}
/**
* Renames a folder
*
* @param string $old_name Old folder name (UTF7-IMAP)
* @param string $new_name New folder name (UTF7-IMAP)
* @param int $type Folder (ActiveSync) type
*
* @return bool True on success, False on failure
*/
public
function
folder_rename
(
$old_name
,
$new_name
,
$type
)
{
$this
->
folder_meta
=
null
;
$type
=
self
::
type_activesync2kolab
(
$type
);
// don't use kolab_storage for moving mail folders
if
(
preg_match
(
'/^mail/'
,
$type
))
{
return
$this
->
storage
->
rename_folder
(
$old_name
,
$new_name
);
}
else
{
return
kolab_storage
::
folder_rename
(
$old_name
,
$new_name
);
}
}
/**
* Deletes folder
*
* @param string $name Folder name (UTF7-IMAP)
* @param string $deviceid Device identifier
*
*/
public
function
folder_delete
(
$name
,
$deviceid
)
{
unset
(
$this
->
folder_meta
[
$name
]);
return
kolab_storage
::
folder_delete
(
$name
);
}
/**
* Sets ActiveSync subscription flag on a folder
*
* @param string $name Folder name (UTF7-IMAP)
* @param string $deviceid Device identifier
* @param int $flag Flag value (0|1|2)
*/
public
function
folder_set
(
$name
,
$deviceid
,
$flag
)
{
if
(
empty
(
$deviceid
))
{
return
false
;
}
// get folders activesync config
$metadata
=
$this
->
folder_meta
();
if
(!
is_array
(
$metadata
))
{
return
false
;
}
$metadata
=
$metadata
[
$name
];
if
(
$flag
)
{
if
(
empty
(
$metadata
))
{
$metadata
=
array
();
}
if
(
empty
(
$metadata
[
'FOLDER'
]))
{
$metadata
[
'FOLDER'
]
=
array
();
}
if
(
empty
(
$metadata
[
'FOLDER'
][
$deviceid
]))
{
$metadata
[
'FOLDER'
][
$deviceid
]
=
array
();
}
// Z-Push uses:
// 1 - synchronize, no alarms
// 2 - synchronize with alarms
$metadata
[
'FOLDER'
][
$deviceid
][
'S'
]
=
$flag
;
}
if
(!
$flag
)
{
unset
(
$metadata
[
'FOLDER'
][
$deviceid
][
'S'
]);
if
(
empty
(
$metadata
[
'FOLDER'
][
$deviceid
]))
{
unset
(
$metadata
[
'FOLDER'
][
$deviceid
]);
}
if
(
empty
(
$metadata
[
'FOLDER'
]))
{
unset
(
$metadata
[
'FOLDER'
]);
}
if
(
empty
(
$metadata
))
{
$metadata
=
null
;
}
}
// Return if nothing's been changed
if
(!
self
::
data_array_diff
(
$this
->
folder_meta
[
$name
],
$metadata
))
{
return
true
;
}
$this
->
folder_meta
[
$name
]
=
$metadata
;
return
$this
->
storage
->
set_metadata
(
$name
,
array
(
self
::
ASYNC_KEY
=>
$this
->
serialize_metadata
(
$metadata
)));
}
public
function
device_get
(
$id
)
{
$devices_list
=
$this
->
devices_list
();
$result
=
$devices_list
[
$id
];
return
$result
;
}
/**
* Registers new device on server
*
* @param array $device Device data
* @param string $id Device ID
*
* @return bool True on success, False on failure
*/
public
function
device_create
(
$device
,
$id
)
{
// Fill local cache
$this
->
devices_list
();
// Some devices create dummy devices with name "validate" (#1109)
// This device entry is used in two initial requests, but later
// the device registers a real name. We can remove this dummy entry
// on new device creation
$this
->
device_delete
(
'validate'
);
// Old Kolab_ZPush device parameters
// MODE: -1 | 0 | 1 (not set | flatmode | foldermode)
// TYPE: device type string
// ALIAS: user-friendly device name
// Syncroton (kolab_sync_backend_device) uses
// ID: internal identifier in syncroton database
// TYPE: device type string
// ALIAS: user-friendly device name
$metadata
=
$this
->
root_meta
;
$metadata
[
'DEVICE'
][
$id
]
=
$device
;
$metadata
=
array
(
self
::
ASYNC_KEY
=>
$this
->
serialize_metadata
(
$metadata
));
$result
=
$this
->
storage
->
set_metadata
(
self
::
ROOT_MAILBOX
,
$metadata
);
if
(
$result
)
{
// Update local cache
$this
->
root_meta
[
'DEVICE'
][
$id
]
=
$device
;
// subscribe default set of folders
$this
->
device_init_subscriptions
(
$id
);
}
return
$result
;
}
/**
* Device update.
*
* @param array $device Device data
* @param string $id Device ID
*
* @return bool True on success, False on failure
*/
public
function
device_update
(
$device
,
$id
)
{
$devices_list
=
$this
->
devices_list
();
$old_device
=
$devices_list
[
$id
];
if
(!
$old_device
)
{
return
false
;
}
// Do nothing if nothing is changed
if
(!
self
::
data_array_diff
(
$old_device
,
$device
))
{
return
true
;
}
$device
=
array_merge
(
$old_device
,
$device
);
$metadata
=
$this
->
root_meta
;
$metadata
[
'DEVICE'
][
$id
]
=
$device
;
$metadata
=
array
(
self
::
ASYNC_KEY
=>
$this
->
serialize_metadata
(
$metadata
));
$result
=
$this
->
storage
->
set_metadata
(
self
::
ROOT_MAILBOX
,
$metadata
);
if
(
$result
)
{
// Update local cache
$this
->
root_meta
[
'DEVICE'
][
$id
]
=
$device
;
}
return
$result
;
}
/**
* Device delete.
*
* @param string $id Device ID
*
* @return bool True on success, False on failure
*/
public
function
device_delete
(
$id
)
{
$device
=
$this
->
device_get
(
$id
);
if
(!
$device
)
{
return
false
;
}
unset
(
$this
->
root_meta
[
'DEVICE'
][
$id
],
$this
->
root_meta
[
'FOLDER'
][
$id
]);
if
(
empty
(
$this
->
root_meta
[
'DEVICE'
]))
{
unset
(
$this
->
root_meta
[
'DEVICE'
]);
}
if
(
empty
(
$this
->
root_meta
[
'FOLDER'
]))
{
unset
(
$this
->
root_meta
[
'FOLDER'
]);
}
$metadata
=
$this
->
serialize_metadata
(
$this
->
root_meta
);
$metadata
=
array
(
self
::
ASYNC_KEY
=>
$metadata
);
// update meta data
$result
=
$this
->
storage
->
set_metadata
(
self
::
ROOT_MAILBOX
,
$metadata
);
if
(
$result
)
{
// remove device annotation for every folder
foreach
(
$this
->
folder_meta
()
as
$folder
=>
$meta
)
{
// skip root folder (already handled above)
if
(
$folder
==
self
::
ROOT_MAILBOX
)
continue
;
if
(!
empty
(
$meta
[
'FOLDER'
])
&&
isset
(
$meta
[
'FOLDER'
][
$id
]))
{
unset
(
$meta
[
'FOLDER'
][
$id
]);
if
(
empty
(
$meta
[
'FOLDER'
]))
{
unset
(
$this
->
folder_meta
[
$folder
][
'FOLDER'
]);
unset
(
$meta
[
'FOLDER'
]);
}
if
(
empty
(
$meta
))
{
unset
(
$this
->
folder_meta
[
$folder
]);
$meta
=
null
;
}
$metadata
=
array
(
self
::
ASYNC_KEY
=>
$this
->
serialize_metadata
(
$meta
));
$res
=
$this
->
storage
->
set_metadata
(
$folder
,
$metadata
);
if
(
$res
&&
$meta
)
{
$this
->
folder_meta
[
$folder
]
=
$meta
;
}
}
}
}
return
$result
;
}
/**
* Subscribe default set of folders on device registration
*/
private
function
device_init_subscriptions
(
$deviceid
)
{
// INBOX always exists
$this
->
folder_set
(
'INBOX'
,
$deviceid
,
1
);
$supported_types
=
array
(
'mail.drafts'
,
'mail.wastebasket'
,
'mail.sentitems'
,
'mail.outbox'
,
'event.default'
,
'contact.default'
,
'note.default'
,
'task.default'
,
'event'
,
'contact'
,
'note'
,
'task'
,
'event.confidential'
,
'event.private'
,
'task.confidential'
,
'task.private'
,
);
// This default set can be extended by adding following values:
$modes
=
array
(
'SUB_PERSONAL'
=>
1
,
// all subscribed folders in personal namespace
'ALL_PERSONAL'
=>
2
,
// all folders in personal namespace
'SUB_OTHER'
=>
4
,
// all subscribed folders in other users namespace
'ALL_OTHER'
=>
8
,
// all folders in other users namespace
'SUB_SHARED'
=>
16
,
// all subscribed folders in shared namespace
'ALL_SHARED'
=>
32
,
// all folders in shared namespace
);
$rcube
=
rcube
::
get_instance
();
$config
=
$rcube
->
config
;
$mode
=
(
int
)
$config
->
get
(
'activesync_init_subscriptions'
);
$folders
=
array
();
// Subscribe to default folders
$foldertypes
=
kolab_storage
::
folders_typedata
();
if
(!
empty
(
$foldertypes
))
{
$_foldertypes
=
array_intersect
(
$foldertypes
,
$supported_types
);
// get default folders
foreach
(
$_foldertypes
as
$folder
=>
$type
)
{
// only personal folders
if
(
$this
->
storage
->
folder_namespace
(
$folder
)
==
'personal'
)
{
$flag
=
preg_match
(
'/^(event|task)/'
,
$type
)
?
2
:
1
;
$this
->
folder_set
(
$folder
,
$deviceid
,
$flag
);
$folders
[]
=
$folder
;
}
}
}
// we're in default mode, exit
if
(!
$mode
)
{
return
;
}
// below we support additionally all mail folders
$supported_types
[]
=
'mail'
;
$supported_types
[]
=
'mail.junkemail'
;
// get configured special folders
$special_folders
=
array
();
$map
=
array
(
'drafts'
=>
'mail.drafts'
,
'junk'
=>
'mail.junkemail'
,
'sent'
=>
'mail.sentitems'
,
'trash'
=>
'mail.wastebasket'
,
);
foreach
(
$map
as
$folder
=>
$type
)
{
if
(
$folder
=
$config
->
get
(
$folder
.
'_mbox'
))
{
$special_folders
[
$folder
]
=
$type
;
}
}
// get folders list(s)
if
((
$mode
&
$modes
[
'ALL_PERSONAL'
])
||
(
$mode
&
$modes
[
'ALL_OTHER'
])
||
(
$mode
&
$modes
[
'ALL_SHARED'
]))
{
$all_folders
=
$this
->
storage
->
list_folders
();
if
((
$mode
&
$modes
[
'SUB_PERSONAL'
])
||
(
$mode
&
$modes
[
'SUB_OTHER'
])
||
(
$mode
&
$modes
[
'SUB_SHARED'
]))
{
$subscribed_folders
=
$this
->
storage
->
list_folders_subscribed
();
}
}
else
{
$all_folders
=
$this
->
storage
->
list_folders_subscribed
();
}
foreach
(
$all_folders
as
$folder
)
{
// folder already subscribed
if
(
in_array
(
$folder
,
$folders
))
{
continue
;
}
$type
=
$foldertypes
[
$folder
]
?:
'mail'
;
if
(
$type
==
'mail'
&&
isset
(
$special_folders
[
$folder
]))
{
$type
=
$special_folders
[
$folder
];
}
if
(!
in_array
(
$type
,
$supported_types
))
{
continue
;
}
$ns
=
strtoupper
(
$this
->
storage
->
folder_namespace
(
$folder
));
// subscribe the folder according to configured mode
// and folder namespace/subscription status
if
((
$mode
&
$modes
[
"ALL_$ns"
])
||
((
$mode
&
$modes
[
"SUB_$ns"
])
&&
(!
isset
(
$subscribed_folders
)
||
in_array
(
$folder
,
$subscribed_folders
)))
)
{
$flag
=
preg_match
(
'/^(event|task)/'
,
$type
)
?
2
:
1
;
$this
->
folder_set
(
$folder
,
$deviceid
,
$flag
);
}
}
}
/**
* Helper method to decode saved IMAP metadata
*/
private
function
unserialize_metadata
(
$str
)
{
if
(!
empty
(
$str
))
{
// Support old Z-Push annotation format
if
(
$str
[
0
]
!=
'{'
)
{
$str
=
base64_decode
(
$str
);
}
$data
=
json_decode
(
$str
,
true
);
return
$data
;
}
return
null
;
}
/**
* Helper method to encode IMAP metadata for saving
*/
private
function
serialize_metadata
(
$data
)
{
if
(!
empty
(
$data
)
&&
is_array
(
$data
))
{
$data
=
json_encode
(
$data
);
// $data = base64_encode($data);
return
$data
;
}
return
null
;
}
/**
* Returns Kolab folder type for specified ActiveSync type ID
*/
public
static
function
type_activesync2kolab
(
$type
)
{
if
(!
empty
(
self
::
$types
[
$type
]))
{
return
self
::
$types
[
$type
];
}
return
''
;
}
/**
* Returns ActiveSync folder type for specified Kolab type
*/
public
static
function
type_kolab2activesync
(
$type
)
{
$type
=
preg_replace
(
'/
\.
(confidential|private)$/i'
,
''
,
$type
);
if
(
$key
=
array_search
(
$type
,
self
::
$types
))
{
return
$key
;
}
return
key
(
self
::
$types
);
}
/**
* Returns Kolab folder type for specified ActiveSync class name
*/
public
static
function
class_activesync2kolab
(
$class
)
{
if
(!
empty
(
self
::
$classes
[
$class
]))
{
return
self
::
$classes
[
$class
];
}
return
''
;
}
/**
* Returns folder data in Syncroton format
*/
private
function
folder_data
(
$folder
,
$type
)
{
// Folder name parameters
$delim
=
$this
->
storage
->
get_hierarchy_delimiter
();
$items
=
explode
(
$delim
,
$folder
);
$name
=
array_pop
(
$items
);
// Folder UID
$folder_id
=
$this
->
folder_id
(
$folder
,
$type
);
// Folder type
if
(
strcasecmp
(
$folder
,
'INBOX'
)
===
0
)
{
// INBOX is always inbox, prevent from issues related with a change of
// folder type annotation (it can be initially unset).
$type
=
2
;
}
else
{
$type
=
self
::
type_kolab2activesync
(
$type
);
// fix type, if there's no type annotation it's detected as UNKNOWN we'll use 'mail' (12)
if
(
$type
==
1
)
{
$type
=
12
;
}
}
// Syncroton folder data array
return
array
(
'serverId'
=>
$folder_id
,
'parentId'
=>
count
(
$items
)
?
self
::
folder_id
(
implode
(
$delim
,
$items
))
:
0
,
'displayName'
=>
rcube_charset
::
convert
(
$name
,
'UTF7-IMAP'
,
kolab_sync
::
CHARSET
),
'type'
=>
$type
,
// for internal use
'imap_name'
=>
$folder
,
);
}
/**
* Builds folder ID based on folder name
*/
public
function
folder_id
(
$name
,
$type
=
null
)
{
// ActiveSync expects folder identifiers to be max.64 characters
// So we can't use just folder name
$name
=
(
string
)
$name
;
if
(
$name
===
''
)
{
return
null
;
}
if
(
isset
(
$this
->
folder_uids
[
$name
]))
{
return
$this
->
folder_uids
[
$name
];
}
/*
@TODO: For now uniqueid annotation doesn't work, we will create UIDs by ourselves.
There's one inconvenience of this solution: folder name/type change
would be handled in ActiveSync as delete + create.
// get folders unique identifier
$folderdata = $this->storage->get_metadata($name, self::UID_KEY);
if ($folderdata && !empty($folderdata[$name])) {
$uid = $folderdata[$name][self::UID_KEY];
return $this->folder_uids[$name] = $uid;
}
*/
if
(
strcasecmp
(
$name
,
'INBOX'
)
===
0
)
{
// INBOX is always inbox, prevent from issues related with a change of
// folder type annotation (it can be initially unset).
$type
=
'mail.inbox'
;
}
else
{
if
(
$type
===
null
)
{
$type
=
kolab_storage
::
folder_type
(
$name
);
}
$type
=
preg_replace
(
'/
\.
(confidential|private)$/i'
,
''
,
$type
);
}
// Add type to folder UID hash, so type change can be detected by Syncroton
$uid
=
$name
.
'!!'
.
$type
;
$uid
=
md5
(
$uid
);
return
$this
->
folder_uids
[
$name
]
=
$uid
;
}
/**
* Returns IMAP folder name
*
* @param string $id Folder identifier
* @param string $deviceid Device dentifier
*
* @return string Folder name (UTF7-IMAP)
*/
public
function
folder_id2name
(
$id
,
$deviceid
)
{
// check in cache first
if
(!
empty
(
$this
->
folder_uids
))
{
if
((
$name
=
array_search
(
$id
,
$this
->
folder_uids
))
!==
false
)
{
return
$name
;
}
}
/*
@TODO: see folder_id()
// get folders unique identifier
$folderdata = $this->storage->get_metadata('*', self::UID_KEY);
foreach ((array)$folderdata as $folder => $data) {
if (!empty($data[self::UID_KEY])) {
$uid = $data[self::UID_KEY];
$this->folder_uids[$folder] = $uid;
if ($uid == $id) {
$name = $folder;
}
}
}
*/
// get all folders of specified type
$folderdata
=
$this
->
folder_meta
();
if
(!
is_array
(
$folderdata
)
||
$id
===
null
)
{
return
null
;
}
// check if folders are "subscribed" for activesync
foreach
(
$folderdata
as
$folder
=>
$meta
)
{
if
(
empty
(
$meta
[
'FOLDER'
])
||
empty
(
$meta
[
'FOLDER'
][
$deviceid
])
||
empty
(
$meta
[
'FOLDER'
][
$deviceid
][
'S'
])
)
{
continue
;
}
if
(
$uid
=
self
::
folder_id
(
$folder
))
{
$this
->
folder_uids
[
$folder
]
=
$uid
;
}
if
(
$uid
===
$id
)
{
$name
=
$folder
;
}
}
return
$name
;
}
/**
*/
public
function
modseq_set
(
$deviceid
,
$folderid
,
$synctime
,
$data
)
{
$synctime
=
$synctime
->
format
(
'Y-m-d H:i:s'
);
$rcube
=
rcube
::
get_instance
();
$db
=
$rcube
->
get_dbh
();
$old_data
=
$this
->
modseq
[
$folderid
][
$synctime
];
if
(
empty
(
$old_data
))
{
$this
->
modseq
[
$folderid
][
$synctime
]
=
$data
;
$data
=
json_encode
(
$data
);
$db
->
set_option
(
'ignore_key_errors'
,
true
);
$db
->
query
(
"INSERT INTO `syncroton_modseq` (`device_id`, `folder_id`, `synctime`, `data`)"
.
" VALUES (?, ?, ?, ?)"
,
$deviceid
,
$folderid
,
$synctime
,
$data
);
$db
->
set_option
(
'ignore_key_errors'
,
false
);
}
}
public
function
modseq_get
(
$deviceid
,
$folderid
,
$synctime
)
{
$synctime
=
$synctime
->
format
(
'Y-m-d H:i:s'
);
if
(
empty
(
$this
->
modseq
[
$folderid
][
$synctime
]))
{
$this
->
modseq
[
$folderid
]
=
array
();
$rcube
=
rcube
::
get_instance
();
$db
=
$rcube
->
get_dbh
();
$db
->
limitquery
(
"SELECT `data`, `synctime` FROM `syncroton_modseq`"
.
" WHERE `device_id` = ? AND `folder_id` = ? AND `synctime` <= ?"
.
" ORDER BY `synctime` DESC"
,
0
,
1
,
$deviceid
,
$folderid
,
$synctime
);
if
(
$row
=
$db
->
fetch_assoc
())
{
$synctime
=
$row
[
'synctime'
];
// @TODO: make sure synctime from sql is in "Y-m-d H:i:s" format
$this
->
modseq
[
$folderid
][
$synctime
]
=
json_decode
(
$row
[
'data'
],
true
);
}
// Cleanup: remove all records except the current one
$db
->
query
(
"DELETE FROM `syncroton_modseq`"
.
" WHERE `device_id` = ? AND `folder_id` = ? AND `synctime` <> ?"
,
$deviceid
,
$folderid
,
$synctime
);
}
return
@
$this
->
modseq
[
$folderid
][
$synctime
];
}
/**
* Set state of relation objects at specified point in time
*/
public
function
relations_state_set
(
$deviceid
,
$folderid
,
$synctime
,
$relations
)
{
$synctime
=
$synctime
->
format
(
'Y-m-d H:i:s'
);
$rcube
=
rcube
::
get_instance
();
$db
=
$rcube
->
get_dbh
();
$old_data
=
$this
->
relations
[
$folderid
][
$synctime
];
if
(
empty
(
$old_data
))
{
$this
->
relations
[
$folderid
][
$synctime
]
=
$relations
;
$data
=
rcube_charset
::
clean
(
json_encode
(
$relations
));
$db
->
set_option
(
'ignore_key_errors'
,
true
);
$db
->
query
(
"INSERT INTO `syncroton_relations_state`"
.
" (`device_id`, `folder_id`, `synctime`, `data`)"
.
" VALUES (?, ?, ?, ?)"
,
$deviceid
,
$folderid
,
$synctime
,
$data
);
$db
->
set_option
(
'ignore_key_errors'
,
false
);
}
}
/**
* Get state of relation objects at specified point in time
*/
public
function
relations_state_get
(
$deviceid
,
$folderid
,
$synctime
)
{
$synctime
=
$synctime
->
format
(
'Y-m-d H:i:s'
);
if
(
empty
(
$this
->
relations
[
$folderid
][
$synctime
]))
{
$this
->
relations
[
$folderid
]
=
array
();
$rcube
=
rcube
::
get_instance
();
$db
=
$rcube
->
get_dbh
();
$db
->
limitquery
(
"SELECT `data`, `synctime` FROM `syncroton_relations_state`"
.
" WHERE `device_id` = ? AND `folder_id` = ? AND `synctime` <= ?"
.
" ORDER BY `synctime` DESC"
,
0
,
1
,
$deviceid
,
$folderid
,
$synctime
);
if
(
$row
=
$db
->
fetch_assoc
())
{
$synctime
=
$row
[
'synctime'
];
// @TODO: make sure synctime from sql is in "Y-m-d H:i:s" format
$this
->
relations
[
$folderid
][
$synctime
]
=
json_decode
(
$row
[
'data'
],
true
);
}
// Cleanup: remove all records except the current one
$db
->
query
(
"DELETE FROM `syncroton_relations_state`"
.
" WHERE `device_id` = ? AND `folder_id` = ? AND `synctime` <> ?"
,
$deviceid
,
$folderid
,
$synctime
);
}
return
@
$this
->
relations
[
$folderid
][
$synctime
];
}
/**
* Return last storage error
*/
public
static
function
last_error
()
{
return
kolab_storage
::
$last_error
;
}
/**
* Compares two arrays
*
* @param array $array1
* @param array $array2
*
* @return bool True if arrays differs, False otherwise
*/
private
static
function
data_array_diff
(
$array1
,
$array2
)
{
if
(!
is_array
(
$array1
)
||
!
is_array
(
$array2
))
{
return
$array1
!=
$array2
;
}
if
(
count
(
$array1
)
!=
count
(
$array2
))
{
return
true
;
}
foreach
(
$array1
as
$key
=>
$val
)
{
if
(!
array_key_exists
(
$key
,
$array2
))
{
return
true
;
}
if
(
$val
!==
$array2
[
$key
])
{
return
true
;
}
}
return
false
;
}
}
File Metadata
Details
Attached
Mime Type
text/x-php
Expires
Sun, Apr 5, 11:56 PM (1 w, 4 d ago)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
89/1e/ccbd1c0c60dc29633fb416a76f4f
Default Alt Text
kolab_sync_backend.php (32 KB)
Attached To
Mode
rS syncroton
Attached
Detach File
Event Timeline