Changeset View
Changeset View
Standalone View
Standalone View
plugins/libkolab/lib/kolab_storage_config.php
Show All 19 Lines | |||||
* GNU Affero General Public License for more details. | * GNU Affero General Public License for more details. | ||||
* | * | ||||
* You should have received a copy of the GNU Affero General Public License | * 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/>. | * along with this program. If not, see <http://www.gnu.org/licenses/>. | ||||
*/ | */ | ||||
class kolab_storage_config | class kolab_storage_config | ||||
{ | { | ||||
const FOLDER_TYPE = 'configuration'; | const FOLDER_TYPE = 'configuration'; | ||||
const MAX_RELATIONS = 499; // should be less than kolab_storage_cache::MAX_RECORDS | |||||
/** | /** | ||||
* Singleton instace of kolab_storage_config | * Singleton instace of kolab_storage_config | ||||
* | * | ||||
* @var kolab_storage_config | * @var kolab_storage_config | ||||
*/ | */ | ||||
static protected $instance; | static protected $instance; | ||||
Show All 15 Lines | static function get_instance() | ||||
} | } | ||||
return self::$instance; | return self::$instance; | ||||
} | } | ||||
/** | /** | ||||
* Private constructor (finds default configuration folder as a config source) | * Private constructor (finds default configuration folder as a config source) | ||||
*/ | */ | ||||
private function __construct() | private function _init() | ||||
{ | { | ||||
if ($this->enabled !== null) { | |||||
return $this->enabled; | |||||
} | |||||
// get all configuration folders | // get all configuration folders | ||||
$this->folders = kolab_storage::get_folders(self::FOLDER_TYPE, false); | $this->folders = kolab_storage::get_folders(self::FOLDER_TYPE, false); | ||||
foreach ($this->folders as $folder) { | foreach ($this->folders as $folder) { | ||||
if ($folder->default) { | if ($folder->default) { | ||||
$this->default = $folder; | $this->default = $folder; | ||||
break; | break; | ||||
} | } | ||||
Show All 10 Lines | private function _init() | ||||
$folder_type = self::FOLDER_TYPE . '.default'; | $folder_type = self::FOLDER_TYPE . '.default'; | ||||
if (kolab_storage::folder_create($folder_name, $folder_type, true)) { | if (kolab_storage::folder_create($folder_name, $folder_type, true)) { | ||||
$this->default = new kolab_storage_folder($folder_name, $folder_type); | $this->default = new kolab_storage_folder($folder_name, $folder_type); | ||||
} | } | ||||
} | } | ||||
// check if configuration folder exist | // check if configuration folder exist | ||||
if ($this->default && $this->default->name) { | return $this->enabled = $this->default && $this->default->name; | ||||
$this->enabled = true; | |||||
} | |||||
} | } | ||||
/** | /** | ||||
* Check wether any configuration storage (folder) exists | * Check wether any configuration storage (folder) exists | ||||
* | * | ||||
* @return bool | * @return bool | ||||
*/ | */ | ||||
public function is_enabled() | public function is_enabled() | ||||
{ | { | ||||
return $this->enabled; | return $this->_init(); | ||||
} | } | ||||
/** | /** | ||||
* Get configuration objects | * Get configuration objects | ||||
* | * | ||||
* @param array $filter Search filter | * @param array $filter Search filter | ||||
* @param bool $default Enable to get objects only from default folder | * @param bool $default Enable to get objects only from default folder | ||||
* @param int $limit Max. number of records (per-folder) | * @param int $limit Max. number of records (per-folder) | ||||
* | * | ||||
* @return array List of objects | * @return array List of objects | ||||
*/ | */ | ||||
public function get_objects($filter = array(), $default = false, $limit = 0) | public function get_objects($filter = array(), $default = false, $limit = 0) | ||||
{ | { | ||||
$list = array(); | $list = array(); | ||||
if (!$this->is_enabled()) { | |||||
return $list; | |||||
} | |||||
foreach ($this->folders as $folder) { | foreach ($this->folders as $folder) { | ||||
// we only want to read from default folder | // we only want to read from default folder | ||||
if ($default && !$folder->default) { | if ($default && !$folder->default) { | ||||
continue; | continue; | ||||
} | } | ||||
// for better performance it's good to assume max. number of records | // for better performance it's good to assume max. number of records | ||||
if ($limit) { | if ($limit) { | ||||
Show All 14 Lines | class kolab_storage_config | ||||
* | * | ||||
* @param string $uid Object UID | * @param string $uid Object UID | ||||
* @param bool $default Enable to get objects only from default folder | * @param bool $default Enable to get objects only from default folder | ||||
* | * | ||||
* @return array Object data | * @return array Object data | ||||
*/ | */ | ||||
public function get_object($uid, $default = false) | public function get_object($uid, $default = false) | ||||
{ | { | ||||
if (!$this->is_enabled()) { | |||||
return; | |||||
} | |||||
foreach ($this->folders as $folder) { | foreach ($this->folders as $folder) { | ||||
// we only want to read from default folder | // we only want to read from default folder | ||||
if ($default && !$folder->default) { | if ($default && !$folder->default) { | ||||
continue; | continue; | ||||
} | } | ||||
if ($object = $folder->get_object($uid)) { | if ($object = $folder->get_object($uid)) { | ||||
return $object; | return $object; | ||||
} | } | ||||
} | } | ||||
} | } | ||||
/** | /** | ||||
* Create/update configuration object | * Create/update configuration object | ||||
* | * | ||||
* @param array $object Object data | * @param array $object Object data | ||||
* @param string $type Object type | * @param string $type Object type | ||||
* | * | ||||
* @return bool True on success, False on failure | * @return bool True on success, False on failure | ||||
*/ | */ | ||||
public function save(&$object, $type) | public function save(&$object, $type) | ||||
{ | { | ||||
if (!$this->enabled) { | if (!$this->is_enabled()) { | ||||
return false; | return false; | ||||
} | } | ||||
$folder = $this->find_folder($object); | $folder = $this->find_folder($object); | ||||
if ($type) { | if ($type) { | ||||
$object['type'] = $type; | $object['type'] = $type; | ||||
} | } | ||||
Show All 18 Lines | public function save(&$object, $type) | ||||
} | } | ||||
return !empty($status); | return !empty($status); | ||||
} | } | ||||
/** | /** | ||||
* Remove configuration object | * Remove configuration object | ||||
* | * | ||||
* @param string $uid Object UID | * @param string|array $object Object array or its UID | ||||
* | * | ||||
* @return bool True on success, False on failure | * @return bool True on success, False on failure | ||||
*/ | */ | ||||
public function delete($uid) | public function delete($object) | ||||
{ | { | ||||
if (!$this->enabled) { | if (!$this->is_enabled()) { | ||||
return false; | return false; | ||||
} | } | ||||
// fetch the object to find folder | // fetch the object to find folder | ||||
$object = $this->get_object($uid); | if (!is_array($object)) { | ||||
$object = $this->get_object($object); | |||||
} | |||||
if (!$object) { | if (!$object) { | ||||
return false; | return false; | ||||
} | } | ||||
$folder = $this->find_folder($object); | $folder = $this->find_folder($object); | ||||
$status = $folder->delete($uid); | $status = $folder->delete($object); | ||||
// on success, update cached tags list | // on success, update cached tags list | ||||
if ($status && is_array($this->tags)) { | if ($status && is_array($this->tags)) { | ||||
foreach ($this->tags as $idx => $tag) { | foreach ($this->tags as $idx => $tag) { | ||||
if ($tag['uid'] == $uid) { | if ($tag['uid'] == $uid) { | ||||
unset($this->tags[$idx]); | unset($this->tags[$idx]); | ||||
break; | break; | ||||
} | } | ||||
} | } | ||||
} | } | ||||
return $status; | return $status; | ||||
} | } | ||||
/** | /** | ||||
* Find folder | * Find folder | ||||
*/ | */ | ||||
public function find_folder($object = array()) | public function find_folder($object = array()) | ||||
{ | { | ||||
if (!$this->is_enabled()) { | |||||
return; | |||||
} | |||||
// find folder object | // find folder object | ||||
if ($object['_mailbox']) { | if ($object['_mailbox']) { | ||||
foreach ($this->folders as $folder) { | foreach ($this->folders as $folder) { | ||||
if ($folder->name == $object['_mailbox']) { | if ($folder->name == $object['_mailbox']) { | ||||
break; | break; | ||||
} | } | ||||
} | } | ||||
} | } | ||||
▲ Show 20 Lines • Show All 296 Lines • ▼ Show 20 Lines | public static function resolve_members(&$tag, $force = true) | ||||
} | } | ||||
return $result; | return $result; | ||||
} | } | ||||
/** | /** | ||||
* Assign tags to kolab objects | * Assign tags to kolab objects | ||||
* | * | ||||
* @param array $records List of kolab objects | * @param array $records List of kolab objects | ||||
* @param bool $no_return Don't return anything | |||||
* | * | ||||
* @return array List of tags | * @return array List of tags | ||||
*/ | */ | ||||
public function apply_tags(&$records) | public function apply_tags(&$records, $no_return = false) | ||||
{ | { | ||||
if (empty($records) && $no_return) { | |||||
return; | |||||
} | |||||
// first convert categories into tags | // first convert categories into tags | ||||
foreach ($records as $i => $rec) { | foreach ($records as $i => $rec) { | ||||
if (!empty($rec['categories'])) { | if (!empty($rec['categories'])) { | ||||
$folder = new kolab_storage_folder($rec['_mailbox']); | $folder = new kolab_storage_folder($rec['_mailbox']); | ||||
if ($object = $folder->get_object($rec['uid'])) { | if ($object = $folder->get_object($rec['uid'])) { | ||||
$tags = $rec['categories']; | $tags = $rec['categories']; | ||||
unset($object['categories']); | unset($object['categories']); | ||||
Show All 14 Lines | public function apply_tags(&$records, $no_return = false) | ||||
if (in_array($uid, (array) $tag['members'])) { | if (in_array($uid, (array) $tag['members'])) { | ||||
$records[$idx]['tags'][] = $tag['name']; | $records[$idx]['tags'][] = $tag['name']; | ||||
} | } | ||||
} | } | ||||
$tags[] = $tag['name']; | $tags[] = $tag['name']; | ||||
} | } | ||||
$tags = array_unique($tags); | $tags = $no_return ? null : array_unique($tags); | ||||
return $tags; | return $tags; | ||||
} | } | ||||
/** | /** | ||||
* Assign links (relations) to kolab objects | |||||
* | |||||
* @param array $records List of kolab objects | |||||
*/ | |||||
public function apply_links(&$records) | |||||
{ | |||||
$links = array(); | |||||
$uids = array(); | |||||
$ids = array(); | |||||
$limit = 25; | |||||
// get list of object UIDs and UIRs map | |||||
foreach ($records as $i => $rec) { | |||||
$uids[] = $rec['uid']; | |||||
// there can be many objects with the same uid (recurring events) | |||||
$ids[self::build_member_url($rec['uid'])][] = $i; | |||||
$records[$i]['links'] = array(); | |||||
} | |||||
if (!empty($uids)) { | |||||
$uids = array_unique($uids); | |||||
} | |||||
// The whole story here is to not do SELECT for every object. | |||||
// We'll build one SELECT for many (limit above) objects at once | |||||
while (!empty($uids)) { | |||||
$chunk = array_splice($uids, 0, $limit); | |||||
$chunk = array_map(function($v) { return array('member', '=', $v); }, $chunk); | |||||
$filter = array( | |||||
array('type', '=', 'relation'), | |||||
array('category', '=', 'generic'), | |||||
array($chunk, 'OR'), | |||||
); | |||||
$relations = $this->get_objects($filter, true, self::MAX_RELATIONS); | |||||
foreach ($relations as $relation) { | |||||
$links[$relation['uid']] = $relation; | |||||
} | |||||
} | |||||
if (empty($links)) { | |||||
return; | |||||
} | |||||
// assign links of related messages | |||||
foreach ($links as $relation) { | |||||
// make relation members up-to-date | |||||
kolab_storage_config::resolve_members($relation); | |||||
$members = array(); | |||||
foreach ((array) $relation['members'] as $member) { | |||||
if (strpos($member, 'imap://') === 0) { | |||||
$members[$member] = $member; | |||||
} | |||||
} | |||||
$members = array_values($members); | |||||
// assign links to objects | |||||
foreach ((array) $relation['members'] as $member) { | |||||
if (($id = $ids[$member]) !== null) { | |||||
foreach ($id as $i) { | |||||
$records[$i]['links'] = array_unique(array_merge($records[$i]['links'], $members)); | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} | |||||
/** | |||||
* Update object tags | * Update object tags | ||||
* | * | ||||
* @param string $uid Kolab object UID | * @param string $uid Kolab object UID | ||||
* @param array $tags List of tag names | * @param array $tags List of tag names | ||||
*/ | */ | ||||
public function save_tags($uid, $tags) | public function save_tags($uid, $tags) | ||||
{ | { | ||||
$url = self::build_member_url($uid); | $url = self::build_member_url($uid); | ||||
▲ Show 20 Lines • Show All 41 Lines • ▼ Show 20 Lines | public function save_tags($uid, $tags) | ||||
} | } | ||||
} | } | ||||
} | } | ||||
/** | /** | ||||
* Get tags (all or referring to specified object) | * Get tags (all or referring to specified object) | ||||
* | * | ||||
* @param string $member Optional object UID or mail message-id | * @param string $member Optional object UID or mail message-id | ||||
* @param int $limit Max. number of records (per-folder) | |||||
* Used when searching by member | |||||
* | * | ||||
* @return array List of Relation objects | * @return array List of Relation objects | ||||
*/ | */ | ||||
public function get_tags($member = '*', $limit = 0) | public function get_tags($member = '*') | ||||
{ | { | ||||
if (!isset($this->tags)) { | if (!isset($this->tags)) { | ||||
$default = true; | $default = true; | ||||
$filter = array( | $filter = array( | ||||
array('type', '=', 'relation'), | array('type', '=', 'relation'), | ||||
array('category', '=', 'tag') | array('category', '=', 'tag') | ||||
); | ); | ||||
// use faster method | // use faster method | ||||
if ($member && $member != '*') { | if ($member && $member != '*') { | ||||
$filter[] = array('member', '=', $member); | $filter[] = array('member', '=', $member); | ||||
$tags = $this->get_objects($filter, $default, $limit); | $tags = $this->get_objects($filter, $default, self::MAX_RELATIONS); | ||||
} | } | ||||
else { | else { | ||||
$this->tags = $tags = $this->get_objects($filter, $default); | $this->tags = $tags = $this->get_objects($filter, $default, self::MAX_RELATIONS); | ||||
} | } | ||||
} | } | ||||
else { | else { | ||||
$tags = $this->tags; | $tags = $this->tags; | ||||
} | } | ||||
if ($member === '*') { | if ($member === '*') { | ||||
return $tags; | return $tags; | ||||
Show All 24 Lines | public function get_tags($member = '*') | ||||
return $result; | return $result; | ||||
} | } | ||||
/** | /** | ||||
* Find objects linked with the given groupware object through a relation | * Find objects linked with the given groupware object through a relation | ||||
* | * | ||||
* @param string Object UUID | * @param string Object UUID | ||||
* @param array List of related URIs | * | ||||
* @return array List of related URIs | |||||
*/ | */ | ||||
public function get_object_links($uid) | public function get_object_links($uid) | ||||
{ | { | ||||
$links = array(); | $links = array(); | ||||
$object_uri = self::build_member_url($uid); | $object_uri = self::build_member_url($uid); | ||||
foreach ($this->get_relations_for_member($uid) as $relation) { | foreach ($this->get_relations_for_member($uid) as $relation) { | ||||
if (in_array($object_uri, (array) $relation['members'])) { | if (in_array($object_uri, (array) $relation['members'])) { | ||||
// make relation members up-to-date | // make relation members up-to-date | ||||
kolab_storage_config::resolve_members($relation); | kolab_storage_config::resolve_members($relation); | ||||
foreach ($relation['members'] as $member) { | foreach ($relation['members'] as $member) { | ||||
if ($member != $object_uri) { | if ($member != $object_uri) { | ||||
$links[] = $member; | $links[] = $member; | ||||
} | } | ||||
} | } | ||||
} | } | ||||
} | } | ||||
return array_unique($links); | return array_unique($links); | ||||
} | } | ||||
/** | /** | ||||
* Save relations of an object. | |||||
* Note, that we already support only one-to-one relations. | |||||
* So, all relations to the object that are not provided in $links | |||||
* argument will be removed. | |||||
* | |||||
* @param string $uid Object UUID | |||||
* @param array $links List of related-object URIs | |||||
* | * | ||||
* @return bool True on success, False on failure | |||||
*/ | */ | ||||
public function save_object_links($uid, $links, $remove = array()) | public function save_object_links($uid, $links) | ||||
{ | { | ||||
$object_uri = self::build_member_url($uid); | $object_uri = self::build_member_url($uid); | ||||
$relations = $this->get_relations_for_member($uid); | $relations = $this->get_relations_for_member($uid); | ||||
$done = false; | $done = false; | ||||
foreach ($relations as $relation) { | foreach ($relations as $relation) { | ||||
// make relation members up-to-date | // make relation members up-to-date | ||||
kolab_storage_config::resolve_members($relation); | kolab_storage_config::resolve_members($relation); | ||||
// remove and add links | // remove and add links | ||||
$members = array_diff($relation['members'], (array)$remove); | $members = array($object_uri); | ||||
$members = array_unique(array_merge($members, $links)); | $members = array_unique(array_merge($members, $links)); | ||||
// make sure the object_uri is still a member | |||||
if (!in_array($object_uri, $members)) { | |||||
$members[$object_uri]; | |||||
} | |||||
// remove relation if no other members remain | // remove relation if no other members remain | ||||
if (count($members) <= 1) { | if (count($members) <= 1) { | ||||
$done = $this->delete($relation['uid']); | $done = $this->delete($relation); | ||||
} | } | ||||
// update relation object if members changed | // update relation object if members changed | ||||
else if (count(array_diff($members, $relation['members'])) || count(array_diff($relation['members'], $members))) { | else if (count(array_diff($members, $relation['members'])) || count(array_diff($relation['members'], $members))) { | ||||
$relation['members'] = $members; | $relation['members'] = $members; | ||||
$done = $this->save($relation, 'relation'); | $done = $this->save($relation, 'relation'); | ||||
$links = array(); | $links = array(); | ||||
} | } | ||||
// no changes, we're happy | // no changes, we're happy | ||||
else { | else { | ||||
$done = true; | $done = true; | ||||
$links = array(); | $links = array(); | ||||
} | } | ||||
} | } | ||||
// create a new relation | // create a new relation | ||||
if (!$done && !empty($links)) { | if (!$done && !empty($links)) { | ||||
$relation = array( | $relation = array( | ||||
'members' => array_merge($links, array($object_uri)), | 'members' => array_merge($links, array($object_uri)), | ||||
'category' => 'generic', | 'category' => 'generic', | ||||
); | ); | ||||
$ret = $this->save($relation, 'relation'); | $done = $this->save($relation, 'relation'); | ||||
} | } | ||||
return $ret; | return $done; | ||||
} | } | ||||
/** | /** | ||||
* Find relation objects referring to specified note | * Find relation objects referring to specified note | ||||
*/ | */ | ||||
public function get_relations_for_member($uid, $reltype = 'generic') | public function get_relations_for_member($uid, $reltype = 'generic') | ||||
{ | { | ||||
$default = true; | $default = true; | ||||
$filter = array( | $filter = array( | ||||
array('type', '=', 'relation'), | array('type', '=', 'relation'), | ||||
array('category', '=', $reltype), | array('category', '=', $reltype), | ||||
array('member', '=', $uid), | array('member', '=', $uid), | ||||
); | ); | ||||
return $this->get_objects($filter, $default, 100); | return $this->get_objects($filter, $default, self::MAX_RELATIONS); | ||||
} | } | ||||
/** | /** | ||||
* Find kolab objects assigned to specified e-mail message | * Find kolab objects assigned to specified e-mail message | ||||
* | * | ||||
* @param rcube_message $message E-mail message | * @param rcube_message $message E-mail message | ||||
* @param string $folder Folder name | * @param string $folder Folder name | ||||
* @param string $type Result objects type | * @param string $type Result objects type | ||||
▲ Show 20 Lines • Show All 47 Lines • ▼ Show 20 Lines | public function get_message_relations($message, $folder, $type) | ||||
} | } | ||||
else { | else { | ||||
$uids = $_cache[$uri]; | $uids = $_cache[$uri]; | ||||
} | } | ||||
// get kolab objects of specified type | // get kolab objects of specified type | ||||
if (!empty($uids)) { | if (!empty($uids)) { | ||||
$query = array(array('uid', '=', array_unique($uids))); | $query = array(array('uid', '=', array_unique($uids))); | ||||
$result = kolab_storage::select($query, $type); | $result = kolab_storage::select($query, $type, count($uids)); | ||||
} | } | ||||
return $result; | return $result; | ||||
} | } | ||||
/** | /** | ||||
* Build a URI representing the given message reference | * Build a URI representing the given message reference | ||||
*/ | */ | ||||
Show All 14 Lines | public static function get_message_uri($headers, $folder) | ||||
} | } | ||||
return self::build_member_url($params); | return self::build_member_url($params); | ||||
} | } | ||||
/** | /** | ||||
* Resolve the email message reference from the given URI | * Resolve the email message reference from the given URI | ||||
*/ | */ | ||||
public function get_message_reference($uri, $rel = null) | public static function get_message_reference($uri, $rel = null) | ||||
{ | { | ||||
if ($linkref = self::parse_member_url($uri)) { | if ($linkref = self::parse_member_url($uri)) { | ||||
$linkref['subject'] = $linkref['params']['subject']; | $linkref['subject'] = $linkref['params']['subject']; | ||||
$linkref['uri'] = $uri; | $linkref['uri'] = $uri; | ||||
$rcmail = rcube::get_instance(); | $rcmail = rcube::get_instance(); | ||||
if (method_exists($rcmail, 'url')) { | if (method_exists($rcmail, 'url')) { | ||||
$linkref['mailurl'] = $rcmail->url(array( | $linkref['mailurl'] = $rcmail->url(array( | ||||
Show All 14 Lines |