Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F86292
kolab_storage_cache.php
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
48 KB
Referenced Files
None
Subscribers
None
kolab_storage_cache.php
View Options
<?php
/**
* Kolab storage cache class providing a local caching layer for Kolab groupware objects.
*
* @version @package_version@
* @author Thomas Bruederli <bruederli@kolabsys.com>
*
* Copyright (C) 2012-2013, 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/>.
*/
class
kolab_storage_cache
{
const
DB_DATE_FORMAT
=
'Y-m-d H:i:s'
;
const
MAX_RECORDS
=
500
;
protected
$db
;
protected
$imap
;
protected
$folder
;
protected
$uid2msg
;
protected
$objects
;
protected
$metadata
=
array
();
protected
$folder_id
;
protected
$resource_uri
;
protected
$enabled
=
true
;
protected
$synched
=
false
;
protected
$synclock
=
false
;
protected
$ready
=
false
;
protected
$cache_table
;
protected
$folders_table
;
protected
$max_sql_packet
;
protected
$max_sync_lock_time
=
600
;
protected
$extra_cols
=
array
();
protected
$data_props
=
array
();
protected
$order_by
=
null
;
protected
$limit
=
null
;
protected
$error
=
0
;
protected
$server_timezone
;
protected
$sync_start
;
/**
* Factory constructor
*/
public
static
function
factory
(
kolab_storage_folder
$storage_folder
)
{
$subclass
=
'kolab_storage_cache_'
.
$storage_folder
->
type
;
if
(
class_exists
(
$subclass
))
{
return
new
$subclass
(
$storage_folder
);
}
else
{
rcube
::
raise_error
(
array
(
'code'
=>
900
,
'type'
=>
'php'
,
'message'
=>
"No kolab_storage_cache class found for folder '$storage_folder->name' of type '$storage_folder->type'"
),
true
);
return
new
kolab_storage_cache
(
$storage_folder
);
}
}
/**
* Default constructor
*/
public
function
__construct
(
kolab_storage_folder
$storage_folder
=
null
)
{
$rcmail
=
rcube
::
get_instance
();
$this
->
db
=
$rcmail
->
get_dbh
();
$this
->
imap
=
$rcmail
->
get_storage
();
$this
->
enabled
=
$rcmail
->
config
->
get
(
'kolab_cache'
,
false
);
$this
->
folders_table
=
$this
->
db
->
table_name
(
'kolab_folders'
);
$this
->
server_timezone
=
new
DateTimeZone
(
date_default_timezone_get
());
if
(
$this
->
enabled
)
{
// always read folder cache and lock state from DB master
$this
->
db
->
set_table_dsn
(
'kolab_folders'
,
'w'
);
// remove sync-lock on script termination
$rcmail
->
add_shutdown_function
(
array
(
$this
,
'_sync_unlock'
));
}
if
(
$storage_folder
)
{
$this
->
set_folder
(
$storage_folder
);
}
}
/**
* Direct access to cache by folder_id
* (only for internal use)
*/
public
function
select_by_id
(
$folder_id
)
{
$query
=
$this
->
db
->
query
(
"SELECT * FROM `{$this->folders_table}` WHERE `folder_id` = ?"
,
$folder_id
);
if
(
$sql_arr
=
$this
->
db
->
fetch_assoc
(
$query
))
{
$this
->
metadata
=
$sql_arr
;
$this
->
folder_id
=
$sql_arr
[
'folder_id'
];
$this
->
folder
=
new
StdClass
;
$this
->
folder
->
type
=
$sql_arr
[
'type'
];
$this
->
resource_uri
=
$sql_arr
[
'resource'
];
$this
->
cache_table
=
$this
->
db
->
table_name
(
'kolab_cache_'
.
$sql_arr
[
'type'
]);
$this
->
ready
=
true
;
}
}
/**
* Connect cache with a storage folder
*
* @param kolab_storage_folder The storage folder instance to connect with
*/
public
function
set_folder
(
kolab_storage_folder
$storage_folder
)
{
$this
->
folder
=
$storage_folder
;
if
(
empty
(
$this
->
folder
->
name
)
||
!
$this
->
folder
->
valid
)
{
$this
->
ready
=
false
;
return
;
}
// compose fully qualified ressource uri for this instance
$this
->
resource_uri
=
$this
->
folder
->
get_resource_uri
();
$this
->
cache_table
=
$this
->
db
->
table_name
(
'kolab_cache_'
.
$this
->
folder
->
type
);
$this
->
ready
=
$this
->
enabled
&&
!
empty
(
$this
->
folder
->
type
);
$this
->
folder_id
=
null
;
}
/**
* Returns true if this cache supports query by type
*/
public
function
has_type_col
()
{
return
in_array
(
'type'
,
$this
->
extra_cols
);
}
/**
* Getter for the numeric ID used in cache tables
*/
public
function
get_folder_id
()
{
$this
->
_read_folder_data
();
return
$this
->
folder_id
;
}
/**
* Returns code of last error
*
* @return int Error code
*/
public
function
get_error
()
{
return
$this
->
error
;
}
/**
* Synchronize local cache data with remote
*/
public
function
synchronize
()
{
// only sync once per request cycle
if
(
$this
->
synched
)
return
;
if
(!
$this
->
ready
)
{
// kolab cache is disabled, synchronize IMAP mailbox cache only
$this
->
imap_mode
(
true
);
$this
->
imap
->
folder_sync
(
$this
->
folder
->
name
);
$this
->
imap_mode
(
false
);
}
else
{
$this
->
sync_start
=
time
();
// read cached folder metadata
$this
->
_read_folder_data
();
// Read folder data from IMAP
$ctag
=
$this
->
folder
->
get_ctag
();
// Validate current ctag
list
(
$uidvalidity
,
$highestmodseq
,
$uidnext
)
=
explode
(
'-'
,
$ctag
);
if
(
empty
(
$uidvalidity
)
||
empty
(
$highestmodseq
))
{
rcube
::
raise_error
(
array
(
'code'
=>
900
,
'message'
=>
"Failed to sync the kolab cache (Invalid ctag)"
),
true
);
}
// check cache status ($this->metadata is set in _read_folder_data())
else
if
(
empty
(
$this
->
metadata
[
'ctag'
])
||
empty
(
$this
->
metadata
[
'changed'
])
||
$this
->
metadata
[
'ctag'
]
!==
$ctag
)
{
// lock synchronization for this folder or wait if locked
$this
->
_sync_lock
();
// Run a full-sync (initial sync or continue the aborted sync)
if
(
empty
(
$this
->
metadata
[
'changed'
])
||
empty
(
$this
->
metadata
[
'ctag'
]))
{
$result
=
$this
->
synchronize_full
();
}
// Synchronize only the changes since last sync
else
{
$result
=
$this
->
synchronize_update
(
$ctag
);
}
// update ctag value (will be written to database in _sync_unlock())
if
(
$result
)
{
$this
->
metadata
[
'ctag'
]
=
$ctag
;
$this
->
metadata
[
'changed'
]
=
date
(
self
::
DB_DATE_FORMAT
,
time
());
}
// remove lock
$this
->
_sync_unlock
();
}
}
$this
->
check_error
();
$this
->
synched
=
time
();
}
/**
* Perform full cache synchronization
*/
protected
function
synchronize_full
()
{
// get effective time limit we have for synchronization (~70% of the execution time)
$time_limit
=
$this
->
_max_sync_lock_time
()
*
0.7
;
if
(
time
()
-
$this
->
sync_start
>
$time_limit
)
{
return
false
;
}
// disable messages cache if configured to do so
$this
->
imap_mode
(
true
);
// synchronize IMAP mailbox cache, does nothing if messages cache is disabled
$this
->
imap
->
folder_sync
(
$this
->
folder
->
name
);
// compare IMAP index with object cache index
$imap_index
=
$this
->
imap
->
index
(
$this
->
folder
->
name
,
null
,
null
,
true
,
true
);
$this
->
imap_mode
(
false
);
if
(
$imap_index
->
is_error
())
{
rcube
::
raise_error
(
array
(
'code'
=>
900
,
'message'
=>
"Failed to sync the kolab cache (SEARCH failed)"
),
true
);
return
false
;
}
// determine objects to fetch or to invalidate
$imap_index
=
$imap_index
->
get
();
$del_index
=
array
();
$old_index
=
$this
->
current_index
(
$del_index
);
// Fetch objects and store in DB
$result
=
$this
->
synchronize_fetch
(
$imap_index
,
$old_index
,
$del_index
);
if
(
$result
)
{
// Remove redundant entries from IMAP and cache
$rem_index
=
array_intersect
(
$del_index
,
$imap_index
);
$del_index
=
array_merge
(
array_unique
(
$del_index
),
array_diff
(
$old_index
,
$imap_index
));
$this
->
synchronize_delete
(
$rem_index
,
$del_index
);
}
return
$result
;
}
/**
* Perform partial cache synchronization, based on QRESYNC
*/
protected
function
synchronize_update
()
{
if
(!
$this
->
imap
->
get_capability
(
'QRESYNC'
))
{
rcube
::
raise_error
(
array
(
'code'
=>
900
,
'message'
=>
"Failed to sync the kolab cache (no QRESYNC capability)"
),
true
);
return
$this
->
synchronize_full
();
}
// Handle the previous ctag
list
(
$uidvalidity
,
$highestmodseq
,
$uidnext
)
=
explode
(
'-'
,
$this
->
metadata
[
'ctag'
]);
if
(
empty
(
$uidvalidity
)
||
empty
(
$highestmodseq
))
{
rcube
::
raise_error
(
array
(
'code'
=>
900
,
'message'
=>
"Failed to sync the kolab cache (Invalid old ctag)"
),
true
);
return
false
;
}
// Enable QRESYNC
$res
=
$this
->
imap
->
conn
->
enable
(
'QRESYNC'
);
if
(
$res
===
false
)
{
rcube
::
raise_error
(
array
(
'code'
=>
900
,
'message'
=>
"Failed to sync the kolab cache (failed to enable QRESYNC/CONDSTORE)"
),
true
);
return
false
;
}
$mbox_data
=
$this
->
imap
->
folder_data
(
$this
->
folder
->
name
);
if
(
empty
(
$mbox_data
))
{
rcube
::
raise_error
(
array
(
'code'
=>
900
,
'message'
=>
"Failed to sync the kolab cache (failed to get folder state)"
),
true
);
return
false
;
}
// Check UIDVALIDITY
if
(
$uidvalidity
!=
$mbox_data
[
'UIDVALIDITY'
])
{
return
$this
->
synchronize_full
();
}
// QRESYNC not supported on specified mailbox
if
(!
empty
(
$mbox_data
[
'NOMODSEQ'
])
||
empty
(
$mbox_data
[
'HIGHESTMODSEQ'
]))
{
rcube
::
raise_error
(
array
(
'code'
=>
900
,
'message'
=>
"Failed to sync the kolab cache (QRESYNC not supported on the folder)"
),
true
);
return
$this
->
synchronize_full
();
}
// Get modified flags and vanished messages
// UID FETCH 1:* (FLAGS) (CHANGEDSINCE 0123456789 VANISHED)
$result
=
$this
->
imap
->
conn
->
fetch
(
$this
->
folder
->
name
,
'1:*'
,
true
,
array
(
'FLAGS'
),
$highestmodseq
,
true
);
$removed
=
array
();
$modified
=
array
();
$existing
=
$this
->
current_index
(
$removed
);
if
(!
empty
(
$result
))
{
foreach
(
$result
as
$msg
)
{
$uid
=
$msg
->
uid
;
// Message marked as deleted
if
(!
empty
(
$msg
->
flags
[
'DELETED'
]))
{
$removed
[]
=
$uid
;
continue
;
}
// Flags changed or new
$modified
[]
=
$uid
;
}
}
$new
=
array_diff
(
$modified
,
$existing
,
$removed
);
$result
=
true
;
if
(!
empty
(
$new
))
{
$result
=
$this
->
synchronize_fetch
(
$new
,
$existing
,
$removed
);
if
(!
$result
)
{
return
false
;
}
}
// VANISHED found?
$mbox_data
=
$this
->
imap
->
folder_data
(
$this
->
folder
->
name
);
// Removed vanished messages from the database
$vanished
=
(
array
)
rcube_imap_generic
::
uncompressMessageSet
(
$mbox_data
[
'VANISHED'
]);
// Remove redundant entries from IMAP and DB
$vanished
=
array_merge
(
$removed
,
array_intersect
(
$vanished
,
$existing
));
$this
->
synchronize_delete
(
$removed
,
$vanished
);
return
$result
;
}
/**
* Fetch objects from IMAP and save into the database
*/
protected
function
synchronize_fetch
(
$new_index
,
&
$old_index
,
&
$del_index
)
{
// get effective time limit we have for synchronization (~70% of the execution time)
$time_limit
=
$this
->
_max_sync_lock_time
()
*
0.7
;
if
(
time
()
-
$this
->
sync_start
>
$time_limit
)
{
return
false
;
}
$i
=
0
;
$aborted
=
false
;
// fetch new objects from imap
foreach
(
array_diff
(
$new_index
,
$old_index
)
as
$msguid
)
{
// Note: We'll store only objects matching the folder type
// anything else will be silently ignored
if
(
$object
=
$this
->
folder
->
read_object
(
$msguid
))
{
// Deduplication: remove older objects with the same UID
// Here we do not resolve conflicts, we just make sure
// the most recent version of the object will be used
if
(
$old_msguid
=
$old_index
[
$object
[
'uid'
]])
{
if
(
$old_msguid
<
$msguid
)
{
$del_index
[]
=
$old_msguid
;
}
else
{
$del_index
[]
=
$msguid
;
continue
;
}
}
$old_index
[
$object
[
'uid'
]]
=
$msguid
;
$this
->
_extended_insert
(
$msguid
,
$object
);
// check time limit and abort sync if running too long
if
(++
$i
%
50
==
0
&&
time
()
-
$this
->
sync_start
>
$time_limit
)
{
$aborted
=
true
;
break
;
}
}
}
$this
->
_extended_insert
(
0
,
null
);
return
$aborted
===
false
;
}
/**
* Remove specified objects from the database and IMAP
*/
protected
function
synchronize_delete
(
$imap_delete
,
$db_delete
)
{
if
(!
empty
(
$imap_delete
))
{
$this
->
imap_mode
(
true
);
$this
->
imap
->
delete_message
(
$imap_delete
,
$this
->
folder
->
name
);
$this
->
imap_mode
(
false
);
}
if
(!
empty
(
$db_delete
))
{
$quoted_ids
=
join
(
','
,
array_map
(
array
(
$this
->
db
,
'quote'
),
$db_delete
));
$this
->
db
->
query
(
"DELETE FROM `{$this->cache_table}` WHERE `folder_id` = ? AND `msguid` IN ($quoted_ids)"
,
$this
->
folder_id
);
}
}
/**
* Return current use->msguid index
*/
protected
function
current_index
(&
$duplicates
=
array
())
{
// read cache index
$sql_result
=
$this
->
db
->
query
(
"SELECT `msguid`, `uid` FROM `{$this->cache_table}` WHERE `folder_id` = ?"
.
" ORDER BY `msguid` DESC"
,
$this
->
folder_id
);
$index
=
$del_index
=
array
();
while
(
$sql_arr
=
$this
->
db
->
fetch_assoc
(
$sql_result
))
{
// Mark all duplicates for removal (note sorting order above)
// Duplicates here should not happen, but they do sometimes
if
(
isset
(
$index
[
$sql_arr
[
'uid'
]]))
{
$duplicates
[]
=
$sql_arr
[
'msguid'
];
}
else
{
$index
[
$sql_arr
[
'uid'
]]
=
$sql_arr
[
'msguid'
];
}
}
return
$index
;
}
/**
* Read a single entry from cache or from IMAP directly
*
* @param string Related IMAP message UID
* @param string Object type to read
* @param string IMAP folder name the entry relates to
* @param array Hash array with object properties or null if not found
*/
public
function
get
(
$msguid
,
$type
=
null
,
$foldername
=
null
)
{
// delegate to another cache instance
if
(
$foldername
&&
$foldername
!=
$this
->
folder
->
name
)
{
$success
=
false
;
if
(
$targetfolder
=
kolab_storage
::
get_folder
(
$foldername
))
{
$success
=
$targetfolder
->
cache
->
get
(
$msguid
,
$type
);
$this
->
error
=
$targetfolder
->
cache
->
get_error
();
}
return
$success
;
}
// load object if not in memory
if
(!
isset
(
$this
->
objects
[
$msguid
]))
{
if
(
$this
->
ready
)
{
$this
->
_read_folder_data
();
$sql_result
=
$this
->
db
->
query
(
"SELECT * FROM `{$this->cache_table}` "
.
"WHERE `folder_id` = ? AND `msguid` = ?"
,
$this
->
folder_id
,
$msguid
);
if
(
$sql_arr
=
$this
->
db
->
fetch_assoc
(
$sql_result
))
{
$this
->
objects
=
array
(
$msguid
=>
$this
->
_unserialize
(
$sql_arr
));
// store only this object in memory (#2827)
}
}
// fetch from IMAP if not present in cache
if
(
empty
(
$this
->
objects
[
$msguid
]))
{
if
(
$object
=
$this
->
folder
->
read_object
(
$msguid
,
$type
?:
'*'
,
$foldername
))
{
$this
->
objects
=
array
(
$msguid
=>
$object
);
$this
->
set
(
$msguid
,
$object
);
}
}
}
$this
->
check_error
();
return
$this
->
objects
[
$msguid
];
}
/**
* Getter for a single Kolab object identified by its UID
*
* @param string $uid Object UID
*
* @return array The Kolab object represented as hash array
*/
public
function
get_by_uid
(
$uid
)
{
$old_order_by
=
$this
->
order_by
;
$old_limit
=
$this
->
limit
;
// set order to make sure we get most recent object version
// set limit to skip count query
$this
->
order_by
=
'`msguid` DESC'
;
$this
->
limit
=
array
(
1
,
0
);
$list
=
$this
->
select
(
array
(
array
(
'uid'
,
'='
,
$uid
)));
// set the order/limit back to defined value
$this
->
order_by
=
$old_order_by
;
$this
->
limit
=
$old_limit
;
if
(!
empty
(
$list
)
&&
!
empty
(
$list
[
0
]))
{
return
$list
[
0
];
}
}
/**
* Insert/Update a cache entry
*
* @param string Related IMAP message UID
* @param mixed Hash array with object properties to save or false to delete the cache entry
* @param string IMAP folder name the entry relates to
*/
public
function
set
(
$msguid
,
$object
,
$foldername
=
null
)
{
if
(!
$msguid
)
{
return
;
}
// delegate to another cache instance
if
(
$foldername
&&
$foldername
!=
$this
->
folder
->
name
)
{
if
(
$targetfolder
=
kolab_storage
::
get_folder
(
$foldername
))
{
$targetfolder
->
cache
->
set
(
$msguid
,
$object
);
$this
->
error
=
$targetfolder
->
cache
->
get_error
();
}
return
;
}
// remove old entry
if
(
$this
->
ready
)
{
$this
->
_read_folder_data
();
$this
->
db
->
query
(
"DELETE FROM `{$this->cache_table}` WHERE `folder_id` = ? AND `msguid` = ?"
,
$this
->
folder_id
,
$msguid
);
}
if
(
$object
)
{
// insert new object data...
$this
->
save
(
$msguid
,
$object
);
}
else
{
// ...or set in-memory cache to false
$this
->
objects
[
$msguid
]
=
$object
;
}
$this
->
check_error
();
}
/**
* Insert (or update) a cache entry
*
* @param int Related IMAP message UID
* @param mixed Hash array with object properties to save or false to delete the cache entry
* @param int Optional old message UID (for update)
*/
public
function
save
(
$msguid
,
$object
,
$olduid
=
null
)
{
// write to cache
if
(
$this
->
ready
)
{
$this
->
_read_folder_data
();
$sql_data
=
$this
->
_serialize
(
$object
);
$sql_data
[
'folder_id'
]
=
$this
->
folder_id
;
$sql_data
[
'msguid'
]
=
$msguid
;
$sql_data
[
'uid'
]
=
$object
[
'uid'
];
$args
=
array
();
$cols
=
array
(
'folder_id'
,
'msguid'
,
'uid'
,
'changed'
,
'data'
,
'tags'
,
'words'
);
$cols
=
array_merge
(
$cols
,
$this
->
extra_cols
);
foreach
(
$cols
as
$idx
=>
$col
)
{
$cols
[
$idx
]
=
$this
->
db
->
quote_identifier
(
$col
);
$args
[]
=
$sql_data
[
$col
];
}
if
(
$olduid
)
{
foreach
(
$cols
as
$idx
=>
$col
)
{
$cols
[
$idx
]
=
"$col = ?"
;
}
$query
=
"UPDATE `{$this->cache_table}` SET "
.
implode
(
', '
,
$cols
)
.
" WHERE `folder_id` = ? AND `msguid` = ?"
;
$args
[]
=
$this
->
folder_id
;
$args
[]
=
$olduid
;
}
else
{
$query
=
"INSERT INTO `{$this->cache_table}` (`created`, "
.
implode
(
', '
,
$cols
)
.
") VALUES ("
.
$this
->
db
->
now
()
.
str_repeat
(
', ?'
,
count
(
$cols
))
.
")"
;
}
$result
=
$this
->
db
->
query
(
$query
,
$args
);
if
(!
$this
->
db
->
affected_rows
(
$result
))
{
rcube
::
raise_error
(
array
(
'code'
=>
900
,
'type'
=>
'php'
,
'message'
=>
"Failed to write to kolab cache"
),
true
);
}
}
// keep a copy in memory for fast access
$this
->
objects
=
array
(
$msguid
=>
$object
);
$this
->
uid2msg
=
array
(
$object
[
'uid'
]
=>
$msguid
);
$this
->
check_error
();
}
/**
* Move an existing cache entry to a new resource
*
* @param string Entry's IMAP message UID
* @param string Entry's Object UID
* @param kolab_storage_folder Target storage folder instance
* @param string Target entry's IMAP message UID
*/
public
function
move
(
$msguid
,
$uid
,
$target
,
$new_msguid
=
null
)
{
if
(
$this
->
ready
&&
$target
)
{
// clear cached uid mapping and force new lookup
unset
(
$target
->
cache
->
uid2msg
[
$uid
]);
// resolve new message UID in target folder
if
(!
$new_msguid
)
{
$new_msguid
=
$target
->
cache
->
uid2msguid
(
$uid
);
}
if
(
$new_msguid
)
{
$this
->
_read_folder_data
();
$this
->
db
->
query
(
"UPDATE `{$this->cache_table}` SET `folder_id` = ?, `msguid` = ? "
.
"WHERE `folder_id` = ? AND `msguid` = ?"
,
$target
->
cache
->
get_folder_id
(),
$new_msguid
,
$this
->
folder_id
,
$msguid
);
$result
=
$this
->
db
->
affected_rows
();
}
}
if
(
empty
(
$result
))
{
// just clear cache entry
$this
->
set
(
$msguid
,
false
);
}
unset
(
$this
->
uid2msg
[
$uid
]);
$this
->
check_error
();
}
/**
* Remove all objects from local cache
*/
public
function
purge
()
{
if
(!
$this
->
ready
)
{
return
true
;
}
$this
->
_read_folder_data
();
$result
=
$this
->
db
->
query
(
"DELETE FROM `{$this->cache_table}` WHERE `folder_id` = ?"
,
$this
->
folder_id
);
return
$this
->
db
->
affected_rows
(
$result
);
}
/**
* Update resource URI for existing cache entries
*
* @param string Target IMAP folder to move it to
*/
public
function
rename
(
$new_folder
)
{
if
(!
$this
->
ready
)
{
return
;
}
if
(
$target
=
kolab_storage
::
get_folder
(
$new_folder
))
{
// resolve new message UID in target folder
$this
->
db
->
query
(
"UPDATE `{$this->folders_table}` SET `resource` = ? "
.
"WHERE `resource` = ?"
,
$target
->
get_resource_uri
(),
$this
->
resource_uri
);
$this
->
check_error
();
}
else
{
$this
->
error
=
kolab_storage
::
ERROR_IMAP_CONN
;
}
}
/**
* Select Kolab objects filtered by the given query
*
* @param array Pseudo-SQL query as list of filter parameter triplets
* triplet: array('<colname>', '<comparator>', '<value>')
* @param boolean Set true to only return UIDs instead of complete objects
* @param boolean Use fast mode to fetch only minimal set of information
* (no xml fetching and parsing, etc.)
*
* @return array List of Kolab data objects (each represented as hash array) or UIDs
*/
public
function
select
(
$query
=
array
(),
$uids
=
false
,
$fast
=
false
)
{
$result
=
$uids
?
array
()
:
new
kolab_storage_dataset
(
$this
);
$count
=
null
;
// read from local cache DB (assume it to be synchronized)
if
(
$this
->
ready
)
{
$this
->
_read_folder_data
();
// fetch full object data on one query if a small result set is expected
$fetchall
=
!
$uids
&&
(
$this
->
limit
?
$this
->
limit
[
0
]
:
(
$count
=
$this
->
count
(
$query
)))
<
self
::
MAX_RECORDS
;
// skip SELECT if we know it will return nothing
if
(
$count
===
0
)
{
return
$result
;
}
$sql_query
=
"SELECT "
.
(
$fetchall
?
'*'
:
"`msguid` AS `_msguid`, `uid`"
)
.
" FROM `{$this->cache_table}` WHERE `folder_id` = ?"
.
$this
->
_sql_where
(
$query
)
.
(!
empty
(
$this
->
order_by
)
?
" ORDER BY "
.
$this
->
order_by
:
''
);
$sql_result
=
$this
->
limit
?
$this
->
db
->
limitquery
(
$sql_query
,
$this
->
limit
[
1
],
$this
->
limit
[
0
],
$this
->
folder_id
)
:
$this
->
db
->
query
(
$sql_query
,
$this
->
folder_id
);
if
(
$this
->
db
->
is_error
(
$sql_result
))
{
if
(
$uids
)
{
return
null
;
}
$result
->
set_error
(
true
);
return
$result
;
}
while
(
$sql_arr
=
$this
->
db
->
fetch_assoc
(
$sql_result
))
{
if
(
$fast
)
{
$sql_arr
[
'fast-mode'
]
=
true
;
}
if
(
$uids
)
{
$this
->
uid2msg
[
$sql_arr
[
'uid'
]]
=
$sql_arr
[
'_msguid'
];
$result
[]
=
$sql_arr
[
'uid'
];
}
else
if
(
$fetchall
&&
(
$object
=
$this
->
_unserialize
(
$sql_arr
)))
{
$result
[]
=
$object
;
}
else
if
(!
$fetchall
)
{
// only add msguid to dataset index
$result
[]
=
$sql_arr
;
}
}
}
// use IMAP
else
{
$filter
=
$this
->
_query2assoc
(
$query
);
$this
->
imap_mode
(
true
);
if
(
$filter
[
'type'
])
{
$search
=
'UNDELETED HEADER X-Kolab-Type '
.
kolab_format
::
KTYPE_PREFIX
.
$filter
[
'type'
];
$index
=
$this
->
imap
->
search_once
(
$this
->
folder
->
name
,
$search
);
}
else
{
$index
=
$this
->
imap
->
index
(
$this
->
folder
->
name
,
null
,
null
,
true
,
true
);
}
$this
->
imap_mode
(
false
);
if
(
$index
->
is_error
())
{
$this
->
check_error
();
if
(
$uids
)
{
return
null
;
}
$result
->
set_error
(
true
);
return
$result
;
}
$index
=
$index
->
get
();
$result
=
$uids
?
$index
:
$this
->
_fetch
(
$index
,
$filter
[
'type'
]);
// TODO: post-filter result according to query
}
// We don't want to cache big results in-memory, however
// if we select only one object here, there's a big chance we will need it later
if
(!
$uids
&&
count
(
$result
)
==
1
)
{
if
(
$msguid
=
$result
[
0
][
'_msguid'
])
{
$this
->
uid2msg
[
$result
[
0
][
'uid'
]]
=
$msguid
;
$this
->
objects
=
array
(
$msguid
=>
$result
[
0
]);
}
}
$this
->
check_error
();
return
$result
;
}
/**
* Get number of objects mathing the given query
*
* @param array $query Pseudo-SQL query as list of filter parameter triplets
* @return integer The number of objects of the given type
*/
public
function
count
(
$query
=
array
())
{
// read from local cache DB (assume it to be synchronized)
if
(
$this
->
ready
)
{
$this
->
_read_folder_data
();
$sql_result
=
$this
->
db
->
query
(
"SELECT COUNT(*) AS `numrows` FROM `{$this->cache_table}` "
.
"WHERE `folder_id` = ?"
.
$this
->
_sql_where
(
$query
),
$this
->
folder_id
);
if
(
$this
->
db
->
is_error
(
$sql_result
))
{
return
null
;
}
$sql_arr
=
$this
->
db
->
fetch_assoc
(
$sql_result
);
$count
=
intval
(
$sql_arr
[
'numrows'
]);
}
// use IMAP
else
{
$filter
=
$this
->
_query2assoc
(
$query
);
$this
->
imap_mode
(
true
);
if
(
$filter
[
'type'
])
{
$search
=
'UNDELETED HEADER X-Kolab-Type '
.
kolab_format
::
KTYPE_PREFIX
.
$filter
[
'type'
];
$index
=
$this
->
imap
->
search_once
(
$this
->
folder
->
name
,
$search
);
}
else
{
$index
=
$this
->
imap
->
index
(
$this
->
folder
->
name
,
null
,
null
,
true
,
true
);
}
$this
->
imap_mode
(
false
);
if
(
$index
->
is_error
())
{
$this
->
check_error
();
return
null
;
}
// TODO: post-filter result according to query
$count
=
$index
->
count
();
}
$this
->
check_error
();
return
$count
;
}
/**
* Define ORDER BY clause for cache queries
*/
public
function
set_order_by
(
$sortcols
)
{
if
(!
empty
(
$sortcols
))
{
$sortcols
=
array_map
(
function
(
$v
)
{
$v
=
trim
(
$v
);
if
(
strpos
(
$v
,
' '
))
{
list
(
$column
,
$order
)
=
explode
(
' '
,
$v
,
2
);
return
"`{$column}` {$order}"
;
}
return
"`{$v}`"
;
},
(
array
)
$sortcols
);
$this
->
order_by
=
join
(
', '
,
$sortcols
);
}
else
{
$this
->
order_by
=
null
;
}
}
/**
* Define LIMIT clause for cache queries
*/
public
function
set_limit
(
$length
,
$offset
=
0
)
{
$this
->
limit
=
array
(
$length
,
$offset
);
}
/**
* Helper method to compose a valid SQL query from pseudo filter triplets
*/
protected
function
_sql_where
(
$query
)
{
$sql_where
=
''
;
foreach
((
array
)
$query
as
$param
)
{
if
(
is_array
(
$param
[
0
]))
{
$subq
=
array
();
foreach
(
$param
[
0
]
as
$q
)
{
$subq
[]
=
preg_replace
(
'/^
\s
*AND
\s
+/i'
,
''
,
$this
->
_sql_where
(
array
(
$q
)));
}
if
(!
empty
(
$subq
))
{
$sql_where
.=
' AND ('
.
implode
(
$param
[
1
]
==
'OR'
?
' OR '
:
' AND '
,
$subq
)
.
')'
;
}
continue
;
}
else
if
(
$param
[
1
]
==
'='
&&
is_array
(
$param
[
2
]))
{
$qvalue
=
'('
.
join
(
','
,
array_map
(
array
(
$this
->
db
,
'quote'
),
$param
[
2
]))
.
')'
;
$param
[
1
]
=
'IN'
;
}
else
if
(
$param
[
1
]
==
'~'
||
$param
[
1
]
==
'LIKE'
||
$param
[
1
]
==
'!~'
||
$param
[
1
]
==
'!LIKE'
)
{
$not
=
(
$param
[
1
]
==
'!~'
||
$param
[
1
]
==
'!LIKE'
)
?
'NOT '
:
''
;
$param
[
1
]
=
$not
.
'LIKE'
;
$qvalue
=
$this
->
db
->
quote
(
'%'
.
preg_replace
(
'/(^
\^
|
\$
$)/'
,
' '
,
$param
[
2
]).
'%'
);
}
else
if
(
$param
[
1
]
==
'~*'
||
$param
[
1
]
==
'!~*'
)
{
$not
=
$param
[
1
][
1
]
==
'!'
?
'NOT '
:
''
;
$param
[
1
]
=
$not
.
'LIKE'
;
$qvalue
=
$this
->
db
->
quote
(
preg_replace
(
'/(^
\^
|
\$
$)/'
,
' '
,
$param
[
2
]).
'%'
);
}
else
if
(
$param
[
0
]
==
'tags'
)
{
$param
[
1
]
=
(
$param
[
1
]
==
'!='
?
'NOT '
:
''
)
.
'LIKE'
;
$qvalue
=
$this
->
db
->
quote
(
'% '
.
$param
[
2
].
' %'
);
}
else
{
$qvalue
=
$this
->
db
->
quote
(
$param
[
2
]);
}
$sql_where
.=
sprintf
(
' AND %s %s %s'
,
$this
->
db
->
quote_identifier
(
$param
[
0
]),
$param
[
1
],
$qvalue
);
}
return
$sql_where
;
}
/**
* Helper method to convert the given pseudo-query triplets into
* an associative filter array with 'equals' values only
*/
protected
function
_query2assoc
(
$query
)
{
// extract object type from query parameter
$filter
=
array
();
foreach
(
$query
as
$param
)
{
if
(
$param
[
1
]
==
'='
)
$filter
[
$param
[
0
]]
=
$param
[
2
];
}
return
$filter
;
}
/**
* Fetch messages from IMAP
*
* @param array List of message UIDs to fetch
* @param string Requested object type or * for all
* @param string IMAP folder to read from
* @return array List of parsed Kolab objects
*/
protected
function
_fetch
(
$index
,
$type
=
null
,
$folder
=
null
)
{
$results
=
new
kolab_storage_dataset
(
$this
);
foreach
((
array
)
$index
as
$msguid
)
{
if
(
$object
=
$this
->
folder
->
read_object
(
$msguid
,
$type
,
$folder
))
{
$results
[]
=
$object
;
$this
->
set
(
$msguid
,
$object
);
}
}
return
$results
;
}
/**
* Helper method to convert the given Kolab object into a dataset to be written to cache
*/
protected
function
_serialize
(
$object
)
{
$data
=
array
();
$sql_data
=
array
(
'changed'
=>
null
,
'tags'
=>
''
,
'words'
=>
''
);
if
(
$object
[
'changed'
])
{
$sql_data
[
'changed'
]
=
date
(
self
::
DB_DATE_FORMAT
,
is_object
(
$object
[
'changed'
])
?
$object
[
'changed'
]->
format
(
'U'
)
:
$object
[
'changed'
]);
}
if
(
$object
[
'_formatobj'
])
{
$xml
=
(
string
)
$object
[
'_formatobj'
]->
write
(
3.0
);
$data
[
'_size'
]
=
strlen
(
$xml
);
$sql_data
[
'tags'
]
=
' '
.
join
(
' '
,
$object
[
'_formatobj'
]->
get_tags
())
.
' '
;
// pad with spaces for strict/prefix search
$sql_data
[
'words'
]
=
' '
.
join
(
' '
,
$object
[
'_formatobj'
]->
get_words
())
.
' '
;
}
// Store only minimal set of object properties
foreach
(
$this
->
data_props
as
$prop
)
{
if
(
isset
(
$object
[
$prop
]))
{
$data
[
$prop
]
=
$object
[
$prop
];
if
(
$data
[
$prop
]
instanceof
DateTimeInterface
)
{
$data
[
$prop
]
=
array
(
'cl'
=>
'DateTime'
,
'dt'
=>
$data
[
$prop
]->
format
(
'Y-m-d H:i:s'
),
'tz'
=>
$data
[
$prop
]->
getTimezone
()->
getName
(),
);
}
}
}
$sql_data
[
'data'
]
=
json_encode
(
rcube_charset
::
clean
(
$data
));
return
$sql_data
;
}
/**
* Helper method to turn stored cache data into a valid storage object
*/
protected
function
_unserialize
(
$sql_arr
)
{
if
(
$sql_arr
[
'fast-mode'
]
&&
!
empty
(
$sql_arr
[
'data'
])
&&
(
$object
=
json_decode
(
$sql_arr
[
'data'
],
true
)))
{
$object
[
'uid'
]
=
$sql_arr
[
'uid'
];
foreach
(
$this
->
data_props
as
$prop
)
{
if
(
isset
(
$object
[
$prop
])
&&
is_array
(
$object
[
$prop
])
&&
$object
[
$prop
][
'cl'
]
==
'DateTime'
)
{
$object
[
$prop
]
=
new
DateTime
(
$object
[
$prop
][
'dt'
],
new
DateTimeZone
(
$object
[
$prop
][
'tz'
]));
}
else
if
(!
isset
(
$object
[
$prop
])
&&
isset
(
$sql_arr
[
$prop
]))
{
$object
[
$prop
]
=
$sql_arr
[
$prop
];
}
}
if
(
$sql_arr
[
'created'
]
&&
empty
(
$object
[
'created'
]))
{
$object
[
'created'
]
=
new
DateTime
(
$sql_arr
[
'created'
]);
}
if
(
$sql_arr
[
'changed'
]
&&
empty
(
$object
[
'changed'
]))
{
$object
[
'changed'
]
=
new
DateTime
(
$sql_arr
[
'changed'
]);
}
$object
[
'_type'
]
=
$sql_arr
[
'type'
]
?:
$this
->
folder
->
type
;
$object
[
'_msguid'
]
=
$sql_arr
[
'msguid'
];
$object
[
'_mailbox'
]
=
$this
->
folder
->
name
;
}
// Fetch object xml
else
{
// FIXME: Because old cache solution allowed storing objects that
// do not match folder type we may end up with invalid objects.
// 2nd argument of read_object() here makes sure they are still
// usable. However, not allowing them here might be also an intended
// solution in future.
$object
=
$this
->
folder
->
read_object
(
$sql_arr
[
'msguid'
],
'*'
);
}
return
$object
;
}
/**
* Write records into cache using extended inserts to reduce the number of queries to be executed
*
* @param int Message UID. Set 0 to commit buffered inserts
* @param array Kolab object to cache
*/
protected
function
_extended_insert
(
$msguid
,
$object
)
{
static
$buffer
=
''
;
$line
=
''
;
$cols
=
array
(
'folder_id'
,
'msguid'
,
'uid'
,
'created'
,
'changed'
,
'data'
,
'tags'
,
'words'
);
if
(
$this
->
extra_cols
)
{
$cols
=
array_merge
(
$cols
,
$this
->
extra_cols
);
}
if
(
$object
)
{
$sql_data
=
$this
->
_serialize
(
$object
);
// Skip multi-folder insert for all databases but MySQL
// In Oracle we can't put long data inline, others we don't support yet
if
(
strpos
(
$this
->
db
->
db_provider
,
'mysql'
)
!==
0
)
{
$extra_args
=
array
();
$params
=
array
(
$this
->
folder_id
,
$msguid
,
$object
[
'uid'
],
$sql_data
[
'changed'
],
$sql_data
[
'data'
],
$sql_data
[
'tags'
],
$sql_data
[
'words'
]);
foreach
(
$this
->
extra_cols
as
$col
)
{
$params
[]
=
$sql_data
[
$col
];
$extra_args
[]
=
'?'
;
}
$cols
=
implode
(
', '
,
array_map
(
function
(
$n
)
{
return
"`{$n}`"
;
},
$cols
));
$extra_args
=
count
(
$extra_args
)
?
', '
.
implode
(
', '
,
$extra_args
)
:
''
;
$result
=
$this
->
db
->
query
(
"INSERT INTO `{$this->cache_table}` ($cols)"
.
" VALUES (?, ?, ?, "
.
$this
->
db
->
now
()
.
", ?, ?, ?, ?$extra_args)"
,
$params
);
if
(!
$this
->
db
->
affected_rows
(
$result
))
{
rcube
::
raise_error
(
array
(
'code'
=>
900
,
'message'
=>
"Failed to write to kolab cache"
),
true
);
}
return
;
}
$values
=
array
(
$this
->
db
->
quote
(
$this
->
folder_id
),
$this
->
db
->
quote
(
$msguid
),
$this
->
db
->
quote
(
$object
[
'uid'
]),
$this
->
db
->
now
(),
$this
->
db
->
quote
(
$sql_data
[
'changed'
]),
$this
->
db
->
quote
(
$sql_data
[
'data'
]),
$this
->
db
->
quote
(
$sql_data
[
'tags'
]),
$this
->
db
->
quote
(
$sql_data
[
'words'
]),
);
foreach
(
$this
->
extra_cols
as
$col
)
{
$values
[]
=
$this
->
db
->
quote
(
$sql_data
[
$col
]);
}
$line
=
'('
.
join
(
','
,
$values
)
.
')'
;
}
if
(
$buffer
&&
(!
$msguid
||
(
strlen
(
$buffer
)
+
strlen
(
$line
)
>
$this
->
max_sql_packet
())))
{
$columns
=
implode
(
', '
,
array_map
(
function
(
$n
)
{
return
"`{$n}`"
;
},
$cols
));
$update
=
implode
(
', '
,
array_map
(
function
(
$i
)
{
return
"`{$i}` = VALUES(`{$i}`)"
;
},
array_slice
(
$cols
,
2
)));
$result
=
$this
->
db
->
query
(
"INSERT INTO `{$this->cache_table}` ($columns) VALUES $buffer"
.
" ON DUPLICATE KEY UPDATE $update"
);
if
(!
$this
->
db
->
affected_rows
(
$result
))
{
rcube
::
raise_error
(
array
(
'code'
=>
900
,
'message'
=>
"Failed to write to kolab cache"
),
true
);
}
$buffer
=
''
;
}
$buffer
.=
(
$buffer
?
','
:
''
)
.
$line
;
}
/**
* Returns max_allowed_packet from mysql config
*/
protected
function
max_sql_packet
()
{
if
(!
$this
->
max_sql_packet
)
{
// mysql limit or max 4 MB
$value
=
$this
->
db
->
get_variable
(
'max_allowed_packet'
,
1048500
);
$this
->
max_sql_packet
=
min
(
$value
,
4
*
1024
*
1024
)
-
2000
;
}
return
$this
->
max_sql_packet
;
}
/**
* Read this folder's ID and cache metadata
*/
protected
function
_read_folder_data
()
{
// already done
if
(!
empty
(
$this
->
folder_id
)
||
!
$this
->
ready
)
return
;
$sql_arr
=
$this
->
db
->
fetch_assoc
(
$this
->
db
->
query
(
"SELECT `folder_id`, `synclock`, `ctag`, `changed`"
.
" FROM `{$this->folders_table}` WHERE `resource` = ?"
,
$this
->
resource_uri
));
if
(
$sql_arr
)
{
$this
->
metadata
=
$sql_arr
;
$this
->
folder_id
=
$sql_arr
[
'folder_id'
];
}
else
{
$this
->
db
->
query
(
"INSERT INTO `{$this->folders_table}` (`resource`, `type`)"
.
" VALUES (?, ?)"
,
$this
->
resource_uri
,
$this
->
folder
->
type
);
$this
->
folder_id
=
$this
->
db
->
insert_id
(
'kolab_folders'
);
$this
->
metadata
=
array
();
}
}
/**
* Check lock record for this folder and wait if locked or set lock
*/
protected
function
_sync_lock
()
{
if
(!
$this
->
ready
)
return
;
$this
->
_read_folder_data
();
// abort if database is not set-up
if
(
$this
->
db
->
is_error
())
{
$this
->
check_error
();
$this
->
ready
=
false
;
return
;
}
$read_query
=
"SELECT `synclock`, `ctag` FROM `{$this->folders_table}` WHERE `folder_id` = ?"
;
$write_query
=
"UPDATE `{$this->folders_table}` SET `synclock` = ? WHERE `folder_id` = ? AND `synclock` = ?"
;
$max_lock_time
=
$this
->
_max_sync_lock_time
();
// wait if locked (expire locks after 10 minutes) ...
// ... or if setting lock fails (another process meanwhile set it)
while
(
(
intval
(
$this
->
metadata
[
'synclock'
]
??
0
)
+
$max_lock_time
>
time
())
||
((
$res
=
$this
->
db
->
query
(
$write_query
,
time
(),
$this
->
folder_id
,
intval
(
$this
->
metadata
[
'synclock'
]
??
0
)))
&&
!(
$affected
=
$this
->
db
->
affected_rows
(
$res
))
)
)
{
usleep
(
500000
);
$this
->
metadata
=
$this
->
db
->
fetch_assoc
(
$this
->
db
->
query
(
$read_query
,
$this
->
folder_id
));
}
$this
->
synclock
=
$affected
>
0
;
}
/**
* Remove lock for this folder
*/
public
function
_sync_unlock
()
{
if
(!
$this
->
ready
||
!
$this
->
synclock
)
return
;
$this
->
db
->
query
(
"UPDATE `{$this->folders_table}` SET `synclock` = 0, `ctag` = ?, `changed` = ? WHERE `folder_id` = ?"
,
$this
->
metadata
[
'ctag'
],
$this
->
metadata
[
'changed'
],
$this
->
folder_id
);
$this
->
synclock
=
false
;
}
protected
function
_max_sync_lock_time
()
{
$limit
=
get_offset_sec
(
ini_get
(
'max_execution_time'
));
if
(
$limit
<=
0
||
$limit
>
$this
->
max_sync_lock_time
)
{
$limit
=
$this
->
max_sync_lock_time
;
}
return
$limit
;
}
/**
* Check IMAP connection error state
*/
protected
function
check_error
()
{
if
((
$err_code
=
$this
->
imap
->
get_error_code
())
<
0
)
{
$this
->
error
=
kolab_storage
::
ERROR_IMAP_CONN
;
if
((
$res_code
=
$this
->
imap
->
get_response_code
())
!==
0
&&
in_array
(
$res_code
,
array
(
rcube_storage
::
NOPERM
,
rcube_storage
::
READONLY
)))
{
$this
->
error
=
kolab_storage
::
ERROR_NO_PERMISSION
;
}
}
else
if
(
$this
->
db
->
is_error
())
{
$this
->
error
=
kolab_storage
::
ERROR_CACHE_DB
;
}
}
/**
* Resolve an object UID into an IMAP message UID
*
* @param string Kolab object UID
* @param boolean Include deleted objects
* @return int The resolved IMAP message UID
*/
public
function
uid2msguid
(
$uid
,
$deleted
=
false
)
{
// query local database if available
if
(!
isset
(
$this
->
uid2msg
[
$uid
])
&&
$this
->
ready
)
{
$this
->
_read_folder_data
();
$sql_result
=
$this
->
db
->
query
(
"SELECT `msguid` FROM `{$this->cache_table}` "
.
"WHERE `folder_id` = ? AND `uid` = ? ORDER BY `msguid` DESC"
,
$this
->
folder_id
,
$uid
);
if
(
$sql_arr
=
$this
->
db
->
fetch_assoc
(
$sql_result
))
{
$this
->
uid2msg
[
$uid
]
=
$sql_arr
[
'msguid'
];
}
}
if
(!
isset
(
$this
->
uid2msg
[
$uid
]))
{
// use IMAP SEARCH to get the right message
$index
=
$this
->
imap
->
search_once
(
$this
->
folder
->
name
,
(
$deleted
?
''
:
'UNDELETED '
)
.
'HEADER SUBJECT '
.
rcube_imap_generic
::
escape
(
$uid
));
$results
=
$index
->
get
();
$this
->
uid2msg
[
$uid
]
=
end
(
$results
);
}
return
$this
->
uid2msg
[
$uid
];
}
/**
* Getter for protected member variables
*/
public
function
__get
(
$name
)
{
if
(
$name
==
'folder_id'
)
{
$this
->
_read_folder_data
();
}
return
$this
->
$name
;
}
/**
* Set Roundcube storage options and bypass messages/indexes cache.
*
* We use skip_deleted and threading settings specific to Kolab,
* we have to change these global settings only temporarily.
* Roundcube cache duplicates information already stored in kolab_cache,
* that's why we can disable it for better performance.
*
* @param bool $force True to start Kolab mode, False to stop it.
*/
public
function
imap_mode
(
$force
=
false
)
{
// remember current IMAP settings
if
(
$force
)
{
$this
->
imap_options
=
array
(
'skip_deleted'
=>
$this
->
imap
->
get_option
(
'skip_deleted'
),
'threading'
=>
$this
->
imap
->
get_threading
(),
);
}
// re-set IMAP settings
$this
->
imap
->
set_threading
(
$force
?
false
:
$this
->
imap_options
[
'threading'
]);
$this
->
imap
->
set_options
(
array
(
'skip_deleted'
=>
$force
?
true
:
$this
->
imap_options
[
'skip_deleted'
],
));
// if kolab cache is disabled do nothing
if
(!
$this
->
enabled
)
{
return
;
}
static
$messages_cache
,
$cache_bypass
;
if
(
$messages_cache
===
null
)
{
$rcmail
=
rcube
::
get_instance
();
$messages_cache
=
(
bool
)
$rcmail
->
config
->
get
(
'messages_cache'
);
$cache_bypass
=
(
int
)
$rcmail
->
config
->
get
(
'kolab_messages_cache_bypass'
);
}
if
(
$messages_cache
)
{
// handle recurrent (multilevel) bypass() calls
if
(
$force
)
{
$this
->
cache_bypassed
+=
1
;
if
(
$this
->
cache_bypassed
>
1
)
{
return
;
}
}
else
{
$this
->
cache_bypassed
-=
1
;
if
(
$this
->
cache_bypassed
>
0
)
{
return
;
}
}
switch
(
$cache_bypass
)
{
case
2
:
// Disable messages and index cache completely
$this
->
imap
->
set_messages_caching
(!
$force
);
break
;
case
3
:
case
1
:
// We'll disable messages cache, but keep index cache (1) or vice-versa (3)
// Default mode is both (MODE_INDEX | MODE_MESSAGE)
$mode
=
$cache_bypass
==
3
?
rcube_imap_cache
::
MODE_MESSAGE
:
rcube_imap_cache
::
MODE_INDEX
;
if
(!
$force
)
{
$mode
|=
$cache_bypass
==
3
?
rcube_imap_cache
::
MODE_INDEX
:
rcube_imap_cache
::
MODE_MESSAGE
;
}
$this
->
imap
->
set_messages_caching
(
true
,
$mode
);
}
}
}
/**
* Converts DateTime or unix timestamp into sql date format
* using server timezone.
*/
protected
function
_convert_datetime
(
$datetime
)
{
if
(
is_object
(
$datetime
))
{
$dt
=
clone
$datetime
;
$dt
->
setTimeZone
(
$this
->
server_timezone
);
return
$dt
->
format
(
self
::
DB_DATE_FORMAT
);
}
else
if
(
$datetime
)
{
return
date
(
self
::
DB_DATE_FORMAT
,
$datetime
);
}
}
}
File Metadata
Details
Attached
Mime Type
text/x-php
Expires
Sat, Nov 23, 10:38 AM (45 m, 17 s)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
80140
Default Alt Text
kolab_storage_cache.php (48 KB)
Attached To
Mode
R14 roundcubemail-plugins-kolab
Attached
Detach File
Event Timeline
Log In to Comment